/*******************************************************************************
 * Copyright (c) 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    chris.gross@us.ibm.com    - initial API and implementation
 *    Chuck.Mastrandrea@sas.com - wordwrapping in bug 222280
 *    smcduff@hotmail.com       - wordwrapping in bug 222280
 *    Claes Rosell<claes.rosell@solme.se> - rowspan in bug 272384
 *    Marco Maccaferri<macca@maccasoft.com> - fixed arrow scrolling in bug 294767
 *    higerinbeijing@gmail.com . fixed selectionEvent.item in bug 286617
 *    balarkrishnan@yahoo.com - fix in bug 298684
 *    Enrico Schnepel<enrico.schnepel@randomice.net> - new API in 238729, bugfix in 294952, 322114
 *    Benjamin Bortfeldt<bbortfeldt@gmail.com> - new tooltip support in 300797
 *    Thomas Halm <thha@fernbach.com> - bugfix in 315397
 *    Justin Dolezy <justin@neckdiagrams.com> - bugfix in 316598 
 *    Cosmin Ghita <cghita@ansis.eu> - bugfix in 323687
 *    Pinard-Legry Guilhaume <guilhaume_pl@yahoo.fr> - bugfix in 267057
 *    Thorsten Schenkel <thorsten.schenkel@compeople.de> - bugfix in 356803
 *******************************************************************************/
package org.foliage.rcp.ui.widgets;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.Accessible;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleControlAdapter;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.TypedListener;
import org.foliage.rcp.ui.custom.GridToolTip;
import org.foliage.rcp.ui.dnd.GridDragSourceEffect;
import org.foliage.rcp.ui.dnd.GridDropTargetEffect;
import org.foliage.rcp.ui.internal.proxy.ScrollBarProxy;
import org.foliage.rcp.ui.internal.proxy.IScrollBarProxy;
import org.foliage.rcp.ui.internal.proxy.NullScrollBarProxy;
import org.foliage.rcp.ui.internal.renderer.DefaultBottomLeftRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultColumnGroupHeaderRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultDropPointRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultEmptyCellRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultEmptyColumnFooterRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultEmptyColumnHeaderRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultEmptyRowHeaderRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultFocusRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultInsertMarkRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultRowHeaderRenderer;
import org.foliage.rcp.ui.internal.renderer.DefaultTopLeftRenderer;
import org.foliage.rcp.ui.internal.renderer.GridCellRenderer;
import org.foliage.rcp.ui.internal.renderer.IInternalWidget;
import org.foliage.rcp.ui.internal.renderer.IRenderer;
import org.foliage.rcp.ui.preference.GridCellSpanManager;

/**
 * <p>
 * NOTE: THIS WIDGET AND ITS API ARE STILL UNDER DEVELOPMENT. THIS IS A PRE-RELEASE ALPHA VERSION. USERS SHOULD EXPECT API CHANGES IN FUTURE VERSIONS.
 * </p>
 * Instances of this class implement a selectable user interface object that displays a list of images and strings and issue notification when selected.
 * <p>
 * The item children that may be added to instances of this class must be of type {@code GridItem}.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>SWT.SINGLE, SWT.MULTI, SWT.NO_FOCUS, SWT.CHECK, SWT.VIRTUAL</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection, DefaultSelection</dd>
 * </dl>
 * 
 * @author chris.gross@us.ibm.com
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class Grid extends Canvas {
	// TODO: figure out better way to allow renderers to trigger events
	// TODO: scroll as necessary when performing drag select (current strategy ok)
	// TODO: need to refactor the way the range select remembers older selection
	// TODO: remember why i decided i needed to refactor the way the range select remembers older selection
	// TODO: need to alter how column drag selection works to allow selection of spanned cells
	// TODO: JAVADOC!
	// TODO: column freezing

	// TODO: Performance - need to cache top index

	/**
	 * Object holding the visible range
	 */
	public static class GridVisibleRange {
		private GridItem[] items = new GridItem[0];
		private GridColumn[] columns = new GridColumn[0];

		/**
		 * @return the current items shown
		 */
		public GridItem[] getItems() {
			return items;
		}

		/**
		 * @return the current columns shown
		 */
		public GridColumn[] getColumns() {
			return columns;
		}
	}

	/**
	 * Accessibility default action for column headers and column group headers.
	 */
	private static final String ACC_COLUMN_DEFAULT_ACTION = "Click";

	/**
	 * Accessibility default action for items.
	 */
	private static final String ACC_ITEM_DEFAULT_ACTION = "Double Click";

	/**
	 * Accessibility expand action for tree items.
	 */
	private static final String ACC_ITEM_ACTION_EXPAND = "Expand";

	/**
	 * Accessibility collapse action for tree items.
	 */
	private static final String ACC_ITEM_ACTION_COLLAPSE = "Collapse";

	/**
	 * Accessibility name for the column group header toggle button.
	 */
	private static final String ACC_TOGGLE_BUTTON_NAME = "Toggle Button";

	/**
	 * Alpha blending value used when drawing the dragged column header.
	 */
	private static final int COLUMN_DRAG_ALPHA = 128;

	/**
	 * Number of pixels below the header to draw the drop point.
	 */
	private static final int DROP_POINT_LOWER_OFFSET = 3;

	/**
	 * Horizontal scrolling increment, in pixels.
	 */
	private static final int HORZ_SCROLL_INCREMENT = 5;

	/**
	 * The area to the left and right of the column boundary/resizer that is still considered the resizer area. This prevents a user from having to be *exactly* over the resizer.
	 */
	private static final int COLUMN_RESIZER_THRESHOLD = 4;

	/**
	 * @see #COLUMN_RESIZER_THRESHOLD
	 */
	private static final int ROW_RESIZER_THRESHOLD = 3;

	/**
	 * The minimum width of a column header.
	 */
	private static final int MIN_COLUMN_HEADER_WIDTH = 20;
	/**
	 * The minimum height of a row header.
	 */
	private static final int MIN_ROW_HEADER_HEIGHT = 10;

	/**
	 * The number used when sizing the row header (i.e. size it for '1000') initially.
	 */
	// private static final int INITIAL_ROW_HEADER_SIZING_VALUE = 39;

	/**
	 * The factor to multiply the current row header sizing value by when determining the next sizing value. Used for performance reasons.
	 */
	// private static final int ROW_HEADER_SIZING_MULTIPLIER = 10;

	/**
	 * Tracks whether the scroll values are correct. If not they will be recomputed in onPaint. This allows us to get a free ride on top of the OS's paint event merging to assure that we don't perform
	 * this expensive operation when unnecessary.
	 */
	private boolean scrollValuesObsolete = false;

	/**
	 * All items in the table, not just root items.
	 */
	private List items = new ArrayList();

	/**
	 * All root items.
	 */
	private List rootItems = new ArrayList();

	/**
	 * List of selected items.
	 */
	private List selectedItems = new ArrayList();

	/**
	 * Reference to the item in focus.
	 */
	private GridItem focusItem;

	private boolean cellSelectionEnabled = false;

	private List selectedCells = new ArrayList();
	private List selectedCellsBeforeRangeSelect = new ArrayList();

	private boolean cellDragSelectionOccuring = false;
	private boolean cellRowDragSelectionOccuring = false;
	private boolean cellColumnDragSelectionOccuring = false;
	private boolean cellDragCTRL = false;
	private boolean followupCellSelectionEventOwed = false;

	private boolean cellSelectedOnLastMouseDown;
	private boolean cellRowSelectedOnLastMouseDown;
	private boolean cellColumnSelectedOnLastMouseDown;

	private GridColumn shiftSelectionAnchorColumn;

	private GridColumn focusColumn;

	private List selectedColumns = new ArrayList();

	/**
	 * This is the column that the user last navigated to, but may not be the focusColumn because that column may be spanned in the current row. This is only used in situations where the user has used
	 * the keyboard to navigate up or down in the table and the focusColumn has switched to a new column because the intended column (was maintained in this var) was spanned. The table will attempt to
	 * set focus back to the intended column during subsequent up/down navigations.
	 */
	private GridColumn intendedFocusColumn;

	/**
	 * List of table columns in creation/index order.
	 */
	private List columns = new ArrayList();

	/**
	 * List of the table columns in the order they are displayed.
	 */
	private List displayOrderedColumns = new ArrayList();

	private GridColumnGroup[] columnGroups = new GridColumnGroup[0];

	/**
	 * Renderer to paint the top left area when both column and row headers are shown.
	 */
	private IRenderer topLeftRenderer = new DefaultTopLeftRenderer();

	/**
	 * Renderer to paint the bottom left area when row headers and column footers are shown
	 */
	private IRenderer bottomLeftRenderer = new DefaultBottomLeftRenderer();

	/**
	 * Renderer used to paint row headers.
	 */
	private IRenderer rowHeaderRenderer = new DefaultRowHeaderRenderer();

	/**
	 * Renderer used to paint empty column headers, used when the columns don't fill the horz space.
	 */
	private IRenderer emptyColumnHeaderRenderer = new DefaultEmptyColumnHeaderRenderer();

	/**
	 * Renderer used to paint empty column footers, used when the columns don't fill the horz space.
	 */
	private IRenderer emptyColumnFooterRenderer = new DefaultEmptyColumnFooterRenderer();

	/**
	 * Renderer used to paint empty cells to fill horz and vert space.
	 */
	private GridCellRenderer emptyCellRenderer = new DefaultEmptyCellRenderer();

	/**
	 * Renderer used to paint empty row headers when the rows don't fill the vertical space.
	 */
	private IRenderer emptyRowHeaderRenderer = new DefaultEmptyRowHeaderRenderer();

	/**
	 * Renderers the UI affordance identifying where the dragged column will be dropped.
	 */
	private IRenderer dropPointRenderer = new DefaultDropPointRenderer();

	/**
	 * Renderer used to paint on top of an already painted row to denote focus.
	 */
	private IRenderer focusRenderer = new DefaultFocusRenderer();

	/**
	 * Are row headers visible?
	 */
	private boolean rowHeaderVisible = false;

	/**
	 * Are column headers visible?
	 */
	private boolean columnHeadersVisible = false;

	/**
	 * Are column footers visible?
	 */
	private boolean columnFootersVisible = false;

	/**
	 * Type of selection behavior. Valid values are SWT.SINGLE and SWT.MULTI.
	 */
	private int selectionType = SWT.SINGLE;

	/**
	 * True if selection highlighting is enabled.
	 */
	private boolean selectionEnabled = true;

	/**
	 * Default height of items. This value is used for <code>GridItem</code>s with a height of -1.
	 */
	private int itemHeight = 1;

	private boolean userModifiedItemHeight = false;

	/**
	 * Width of each row header.
	 */
	private int rowHeaderWidth = 0;

	/**
	 * The row header width is variable. The row header width gets larger as more rows are added to the table to ensure that the row header has enough room to display the longest string of numbers
	 * that display in the row header. This determination of how wide to make the row header is rather slow and therefore is only done at every 1000 items (or so). This variable remembers how many
	 * items were last computed and therefore when the number of items is greater than this value, we need to recalculate the row header width. See newItem().
	 */
	// private int lastRowHeaderWidthCalculationAt = 0;

	/**
	 * Height of each column header.
	 */
	private int headerHeight = 0;

	/**
	 * Height of each column footer
	 */
	private int footerHeight = 0;

	/**
	 * True if mouse is hover on a column boundary and can resize the column.
	 */
	private boolean hoveringOnColumnResizer = false;

	/**
	 * Reference to the column being resized.
	 */
	private GridColumn columnBeingResized;

	/**
	 * Are this <code>Grid</code>'s rows resizeable?
	 */
	private boolean rowsResizeable = false;

	/**
	 * Is the user currently resizing a column?
	 */
	private boolean resizingColumn = false;

	/**
	 * The mouse X position when the user starts the resize.
	 */
	private int resizingStartX = 0;

	/**
	 * The width of the column when the user starts the resize. This, together with the resizingStartX determines the current width during resize.
	 */
	private int resizingColumnStartWidth = 0;

	private boolean hoveringOnRowResizer = false;
	private GridItem rowBeingResized;
	private boolean resizingRow = false;
	private int resizingStartY;
	private int resizingRowStartHeight;

	/**
	 * Reference to the column whose header is currently in a pushed state.
	 */
	private GridColumn columnBeingPushed;

	/**
	 * Is the user currently pushing a column header?
	 */
	private boolean pushingColumn = false;

	/**
	 * Is the user currently pushing a column header and hovering over that same header?
	 */
	private boolean pushingAndHovering = false;

	/**
	 * X position of the mouse when the user first pushes a column header.
	 */
	private int startHeaderPushX = 0;

	/**
	 * X position of the mouse when the user has initiated a drag. This is different than startHeaderPushX because the mouse is allowed some 'wiggle-room' until the header is put into drag mode.
	 */
	private int startHeaderDragX = 0;

	/**
	 * The current X position of the mouse during a header drag.
	 */
	private int currentHeaderDragX = 0;

	/**
	 * Are we currently dragging a column header?
	 */
	private boolean draggingColumn = false;

	private GridColumn dragDropBeforeColumn = null;

	private GridColumn dragDropAfterColumn = null;

	/**
	 * True if the current dragDropPoint is a valid drop point for the dragged column. This is false if the column groups are involved and a column is being dropped into or out of its column group.
	 */
	private boolean dragDropPointValid = true;

	/**
	 * Reference to the currently item that the mouse is currently hovering over.
	 */
	private GridItem hoveringItem;

	/**
	 * Reference to the column that the mouse is currently hovering over. Includes the header and all cells (all rows) in this column.
	 */
	private GridColumn hoveringColumn;

	private GridColumn hoveringColumnHeader;

	private GridColumnGroup hoverColumnGroupHeader;

	/**
	 * String-based detail of what is being hovered over in a cell. This allows a renderer to differentiate between hovering over different parts of the cell. For example, hovering over a checkbox in
	 * the cell or hovering over a tree node in the cell. The table does nothing with this string except to set it back in the renderer when its painted. The renderer sets this during its notify
	 * method (InternalWidget.HOVER) and the table pulls it back and maintains it so it can be set back when the cell is painted. The renderer determines what the hover detail means and how it affects
	 * painting.
	 */
	private String hoveringDetail = "";

	/**
	 * True if the mouse is hovering of a cell's text.
	 */
	private boolean hoveringOverText = false;

	/**
	 * Are the grid lines visible?
	 */
	private boolean linesVisible = true;

	/**
	 * Are tree lines visible?
	 */
	private boolean treeLinesVisible = true;

	/**
	 * Grid line color.
	 */
	private Color lineColor;

	/**
	 * Vertical scrollbar proxy.
	 * <p>
	 * Note:
	 * <ul>
	 * <li>{@link Grid#getTopIndex()} is the only method allowed to call vScroll.getSelection() (except #updateScrollbars() of course)</li>
	 * <li>{@link Grid#setTopIndex(int)} is the only method allowed to call vScroll.setSelection(int)</li>
	 * </ul>
	 */
	private IScrollBarProxy vScroll;

	/**
	 * Horizontal scrollbar proxy.
	 */
	private IScrollBarProxy hScroll;

	/**
	 * The number of GridItems whose visible = true. Maintained for performance reasons (rather than iterating over all items).
	 */
	private int currentVisibleItems = 0;

	/**
	 * Item selected when a multiple selection using shift+click first occurs. This item anchors all further shift+click selections.
	 */
	private GridItem shiftSelectionAnchorItem;

	private boolean columnScrolling = false;

	private int groupHeaderHeight;

	private Color cellHeaderSelectionBackground;

	/**
	 * Dispose listener. This listener is removed during the dispose event to allow re-firing of the event.
	 */
	private Listener disposeListener;

	/**
	 * The inplace tooltip.
	 */
	private GridToolTip inplaceToolTip;

	private GC sizingGC;

	private Color backgroundColor;

	/**
	 * True if the widget is being disposed. When true, events are not fired.
	 */
	private boolean disposing = false;

	/**
	 * True if there is at least one tree node. This is used by accessibility and various places for optimization.
	 */
	private boolean isTree = false;

	/**
	 * True if there is at least one <code>GridItem</code> with an individual height. This value is only set to true in {@link GridItem#setHeight(int,boolean)} and it is never reset to false.
	 */
	boolean hasDifferingHeights = false;

	/**
	 * True if three is at least one cell spanning columns. This is used in various places for optimizatoin.
	 */
	private boolean hasSpanning = false;

	/**
	 * Index of first visible item. The value must never be read directly. It is cached and updated when appropriate. #getTopIndex should be called for every client (even internal callers). A value of
	 * -1 indicates that the value is old and will be recomputed.
	 * 
	 * @see #bottomIndex
	 */
	int topIndex = -1;
	/**
	 * Index of last visible item. The value must never be read directly. It is cached and updated when appropriate. #getBottomIndex() should be called for every client (even internal callers). A
	 * value of -1 indicates that the value is old and will be recomputed.
	 * <p>
	 * Note that the item with this index is often only partly visible; maybe only a single line of pixels is visible. In extreme cases, bottomIndex may be the same as topIndex.
	 * 
	 * @see #topIndex
	 */
	int bottomIndex = -1;

	/**
	 * Index of the first visible column. A value of -1 indicates that the value is old and will be recomputed.
	 */
	int startColumnIndex = -1;

	/**
	 * Index of the the last visible column. A value of -1 indicates that the value is old and will be recomputed.
	 */
	int endColumnIndex = -1;

	/**
	 * True if the last visible item is completely visible. The value must never be read directly. It is cached and updated when appropriate. #isShown() should be called for every client (even
	 * internal callers).
	 * 
	 * @see #bottomIndex
	 */
	private boolean bottomIndexShownCompletely = false;

	/**
	 * Tooltip text - overriden because we have cell specific tooltips
	 */
	private String toolTipText = null;

	/**
	 * Flag that is set to true as soon as one image is set on any one item. This is used to mimic Table behavior that resizes the rows on the first image added. See imageSetOnItem.
	 */
	private boolean firstImageSet = false;

	/**
	 * Mouse capture flag. Used for inplace tooltips. This flag must be used to ensure that we don't setCapture(false) in situations where we didn't do setCapture(true). The OS (SWT?) will
	 * automatically capture the mouse for us during a drag operation.
	 */
	private boolean inplaceTooltipCapture;

	/**
	 * This is the tooltip text currently used. This could be the tooltip text for the currently hovered cell, or the general grid tooltip. See handleCellHover.
	 */
	private String displayedToolTipText;

	/**
	 * The height of the area at the top and bottom of the visible grid area in which scrolling is initiated while dragging over this Grid.
	 */
	private static final int DRAG_SCROLL_AREA_HEIGHT = 12;

	/**
	 * Threshold for the selection border used for drag n drop in mode (!{@link #dragOnFullSelection} .
	 */
	private static final int SELECTION_DRAG_BORDER_THRESHOLD = 2;

	private boolean hoveringOnSelectionDragArea = false;

	private GridItem insertMarkItem = null;
	private GridColumn insertMarkColumn = null;
	private boolean insertMarkBefore = false;
	private IRenderer insertMarkRenderer = new DefaultInsertMarkRenderer();
	private boolean sizeOnEveryItemImageChange;
	private boolean autoHeight = false;
	private boolean autoWidth = true;
	private boolean wordWrapRowHeader = false;

	/**
	 * A range of rows in a <code>Grid</code>.
	 * <p>
	 * A row in this sense exists only for visible items (i.e. items with {@link GridItem#isVisible()} == true). Therefore, the items at 'startIndex' and 'endIndex' are always visible.
	 * 
	 * @see Grid#getRowRange(int, int, boolean, boolean)
	 */
	private static class RowRange {
		/** index of first item in range */
		public int startIndex;
		/** index of last item in range */
		public int endIndex;
		/** number of rows (i.e. <em>visible</em> items) in this range */
		public int rows;
		/** height in pixels of this range (including horizontal separator between rows) */
		public int height;
	}

	/**
	 * Filters out unnecessary styles, adds mandatory styles and generally manages the style to pass to the super class.
	 * 
	 * @param style user specified style.
	 * @return style to pass to the super class.
	 */
	private static int checkStyle(int style) {
		int mask = SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.MULTI | SWT.NO_FOCUS | SWT.CHECK | SWT.VIRTUAL;
		int newStyle = style & mask;
		newStyle |= SWT.DOUBLE_BUFFERED;
		return newStyle;
	}

	/**
	 * Constructs a new instance of this class given its parent and a style value describing its behavior and appearance.
	 * <p>
	 * 
	 * @param parent a composite control which will be the parent of the new instance (cannot be null)
	 * @param style the style of control to construct
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
	 *             </ul>
	 * @see SWT#SINGLE
	 * @see SWT#MULTI
	 */
	public Grid(Composite parent, int style) {
		super(parent, checkStyle(style));

		// initialize drag & drop support
		setData("DEFAULT_DRAG_SOURCE_EFFECT", new GridDragSourceEffect(this));
		setData("DEFAULT_DROP_TARGET_EFFECT", new GridDropTargetEffect(this));

		sizingGC = new GC(this);

		topLeftRenderer.setDisplay(getDisplay());
		bottomLeftRenderer.setDisplay(getDisplay());
		rowHeaderRenderer.setDisplay(getDisplay());
		emptyColumnHeaderRenderer.setDisplay(getDisplay());
		emptyColumnFooterRenderer.setDisplay(getDisplay());
		emptyCellRenderer.setDisplay(getDisplay());
		dropPointRenderer.setDisplay(getDisplay());
		focusRenderer.setDisplay(getDisplay());
		emptyRowHeaderRenderer.setDisplay(getDisplay());
		insertMarkRenderer.setDisplay(getDisplay());

		setForeground(getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND));
		setLineColor(getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));

		if ((style & SWT.MULTI) != 0) {
			selectionType = SWT.MULTI;
		}

		if (getVerticalBar() != null) {
			getVerticalBar().setVisible(false);
			vScroll = new ScrollBarProxy(getVerticalBar());
		} else {
			vScroll = new NullScrollBarProxy();
		}

		if (getHorizontalBar() != null) {
			getHorizontalBar().setVisible(false);
			hScroll = new ScrollBarProxy(getHorizontalBar());
		} else {
			hScroll = new NullScrollBarProxy();
		}

		scrollValuesObsolete = true;

		initListeners();
		initAccessible();

		itemHeight = sizingGC.getFontMetrics().getHeight() + 2;

		RGB sel = getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION).getRGB();
		RGB white = getDisplay().getSystemColor(SWT.COLOR_WHITE).getRGB();

		RGB cellSel = blend(sel, white, 50);

		cellHeaderSelectionBackground = new Color(getDisplay(), cellSel);

		setDragDetect(false);
	}

	/**
	 * {@inheritDoc}
	 */
	public Color getBackground() {
		checkWidget();
		if (backgroundColor == null) return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
		return backgroundColor;
	}

	/**
	 * {@inheritDoc}
	 */
	public void setBackground(Color color) {
		checkWidget();
		backgroundColor = color;
		redraw();
	}

	/**
	 * Returns the background color of column and row headers when a cell in the row or header is selected.
	 * 
	 * @return cell header selection background color
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public Color getCellHeaderSelectionBackground() {
		checkWidget();
		return cellHeaderSelectionBackground;
	}

	/**
	 * Sets the background color of column and row headers displayed when a cell in the row or header is selected.
	 * 
	 * @param cellSelectionBackground color to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setCellHeaderSelectionBackground(Color cellSelectionBackground) {
		checkWidget();
		this.cellHeaderSelectionBackground = cellSelectionBackground;
	}

	/**
	 * Adds the listener to the collection of listeners who will be notified when the receiver's selection changes, by sending it one of the messages defined in the {@code SelectionListener}
	 * interface.
	 * <p>
	 * Cell selection events may have <code>Event.detail = SWT.DRAG</code> when the user is drag selecting multiple cells. A follow up selection event will be generated when the drag is complete.
	 * 
	 * @param listener the listener which should be notified
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void addSelectionListener(SelectionListener listener) {
		checkWidget();
		if (listener == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		addListener(SWT.Selection, new TypedListener(listener));
		addListener(SWT.DefaultSelection, new TypedListener(listener));
	}

	/**
	 * Adds the listener to the collection of listeners who will be notified when the receiver's items changes, by sending it one of the messages defined in the {@code TreeListener} interface.
	 * 
	 * @param listener the listener which should be notified
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see TreeListener
	 * @see #removeTreeListener
	 * @see org.eclipse.swt.events.TreeEvent
	 */
	public void addTreeListener(TreeListener listener) {
		checkWidget();
		if (listener == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		addListener(SWT.Expand, new TypedListener(listener));
		addListener(SWT.Collapse, new TypedListener(listener));
	}

	@Override
	public void redraw() {
		super.redraw(getClientArea().x, getClientArea().y, getClientArea().width, getClientArea().height, true);
	}

	/**
	 * {@inheritDoc}
	 */
	public Point computeSize(int wHint, int hHint, boolean changed) {
		checkWidget();

		Point prefSize = null;
		if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
			prefSize = getTableSize();
			prefSize.x += 2 * getBorderWidth();
			prefSize.y += 2 * getBorderWidth();
		}

		int x = 0;
		int y = 0;

		if (wHint == SWT.DEFAULT) {
			x += prefSize.x;
			if (getVerticalBar() != null) {
				x += getVerticalBar().getSize().x;
			}
		} else {
			x = wHint;
		}

		if (hHint == SWT.DEFAULT) {
			y += prefSize.y;
			if (getHorizontalBar() != null) {
				y += getHorizontalBar().getSize().y;
			}
		} else {
			y = hHint;
		}

		return new Point(x, y);
	}

	/**
	 * Deselects the item at the given zero-relative index in the receiver. If the item at the index was already deselected, it remains deselected. Indices that are out of range are ignored.
	 * <p>
	 * If cell selection is enabled, all cells in the specified item are deselected.
	 * 
	 * @param index the index of the item to deselect
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void deselect(int index) {
		checkWidget();

		if (index < 0 || index > items.size() - 1) {
			return;
		}

		GridItem item = (GridItem) items.get(index);

		if (!cellSelectionEnabled) {
			if (selectedItems.contains(item)) {
				selectedItems.remove(item);
			}
		} else {
			deselectCells(getCells(item));
		}
		redraw();
	}

	/**
	 * Deselects the items at the given zero-relative indices in the receiver. If the item at the given zero-relative index in the receiver is selected, it is deselected. If the item at the index was
	 * not selected, it remains deselected. The range of the indices is inclusive. Indices that are out of range are ignored.
	 * <p>
	 * If cell selection is enabled, all cells in the given range are deselected.
	 * 
	 * @param start the start index of the items to deselect
	 * @param end the end index of the items to deselect
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void deselect(int start, int end) {
		checkWidget();

		for (int i = start; i <= end; i++) {
			if (i < 0) {
				continue;
			}
			if (i > items.size() - 1) {
				break;
			}

			GridItem item = (GridItem) items.get(i);

			if (!cellSelectionEnabled) {
				if (selectedItems.contains(item)) {
					selectedItems.remove(item);
				}
			} else {
				deselectCells(getCells(item));
			}
		}
		redraw();
	}

	/**
	 * Deselects the items at the given zero-relative indices in the receiver. If the item at the given zero-relative index in the receiver is selected, it is deselected. If the item at the index was
	 * not selected, it remains deselected. Indices that are out of range and duplicate indices are ignored.
	 * <p>
	 * If cell selection is enabled, all cells in the given items are deselected.
	 * 
	 * @param indices the array of indices for the items to deselect
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the set of indices is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void deselect(int[] indices) {
		checkWidget();
		if (indices == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		for (int i = 0; i < indices.length; i++) {
			int j = indices[i];

			if (j >= 0 && j < items.size()) {
				GridItem item = (GridItem) items.get(j);

				if (!cellSelectionEnabled) {
					if (selectedItems.contains(item)) {
						selectedItems.remove(item);
					}
				} else {
					deselectCells(getCells(item));
				}
			}
		}
		redraw();
	}

	/**
	 * Deselects all selected items in the receiver. If cell selection is enabled, all cells are deselected.
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void deselectAll() {
		checkWidget();

		if (!cellSelectionEnabled) {
			selectedItems.clear();
			redraw();
		} else {
			deselectAllCells();
		}
	}

	/**
	 * Returns the column at the given, zero-relative index in the receiver. Throws an exception if the index is out of range. If no {@code GridColumn}s were created by the programmer, this method
	 * will throw {@code ERROR_INVALID_RANGE} despite the fact that a single column of data may be visible in the table. This occurs when the programmer uses the table like a list, adding items but
	 * never creating a column.
	 * 
	 * @param index the index of the column to return
	 * @return the column at the given index
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridColumn getColumn(int index) {
		checkWidget();

		if (index < 0 || index > getColumnCount() - 1) {
			SWT.error(SWT.ERROR_INVALID_RANGE);
		}

		return (GridColumn) columns.get(index);
	}

	/**
	 * Returns the column at the given point in the receiver or null if no such column exists. The point is in the coordinate system of the receiver.
	 * 
	 * @param point the point used to locate the column
	 * @return the column at the given point
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the point is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridColumn getColumn(Point point) {
		return getColumn(null, point);
	}

	/**
	 * Returns the column at the given point and a known item in the receiver or null if no such column exists. The point is in the coordinate system of the receiver.
	 * 
	 * @param item a known GridItem
	 * @param point the point used to locate the column
	 * @return the column at the given point
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the point is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	private GridColumn getColumn(GridItem item, Point point) {
		checkWidget();
		if (point == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		GridColumn overThis = null;

		int x2 = 0;

		if (rowHeaderVisible) {
			if (point.x <= rowHeaderWidth) {
				return null;
			}

			x2 += rowHeaderWidth;
		}

		x2 -= getHScrollSelectionInPixels();

		for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
			GridColumn column = (GridColumn) columnIterator.next();

			if (!column.isVisible()) {
				continue;
			}

			if (point.x >= x2 && point.x < x2 + column.getWidth()) {
				overThis = column;
				break;
			}

			x2 += column.getWidth();
		}

		if (overThis == null) {
			return null;
		}

		if (hasSpanning) {
			// special logic for column spanning
			if (item == null) {
				item = getItem(point);
			}

			if (item != null) {
				int displayColIndex = displayOrderedColumns.indexOf(overThis);

				// track back all previous columns and check their spanning
				for (int i = 0; i < displayColIndex; i++) {
					if (!((GridColumn) displayOrderedColumns.get(i)).isVisible()) {
						continue;
					}

					int colIndex = indexOf((GridColumn) displayOrderedColumns.get(i));
					int span = item.getColumnSpan(colIndex);

					if (i + span >= displayColIndex) {
						overThis = (GridColumn) displayOrderedColumns.get(i);
						break;
					}
				}
			}
		}

		return overThis;
	}

	/**
	 * Returns the number of columns contained in the receiver. If no {@code GridColumn}s were created by the programmer, this value is zero, despite the fact that visually, one column of items may be
	 * visible. This occurs when the programmer uses the table like a list, adding items but never creating a column.
	 * 
	 * @return the number of columns
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getColumnCount() {
		checkWidget();
		return columns.size();
	}

	/**
	 * Returns an array of zero-relative integers that map the creation order of the receiver's items to the order in which they are currently being displayed.
	 * <p>
	 * Specifically, the indices of the returned array represent the current visual order of the items, and the contents of the array represent the creation order of the items.
	 * </p>
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its list of items, so modifying the array will not affect the receiver.
	 * </p>
	 * 
	 * @return the current visual order of the receiver's items
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int[] getColumnOrder() {
		checkWidget();

		int[] order = new int[columns.size()];
		int i = 0;
		for (Iterator colIterator = displayOrderedColumns.iterator(); colIterator.hasNext();) {
			GridColumn col = (GridColumn) colIterator.next();
			order[i] = columns.indexOf(col);
			i++;
		}
		return order;
	}

	/**
	 * Returns the number of column groups contained in the receiver.
	 * 
	 * @return the number of column groups
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getColumnGroupCount() {
		checkWidget();
		return columnGroups.length;
	}

	/**
	 * Returns an array of {@code GridColumnGroup}s which are the column groups in the receiver.
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its list of items, so modifying the array will not affect the receiver.
	 * </p>
	 * 
	 * @return the column groups in the receiver
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridColumnGroup[] getColumnGroups() {
		checkWidget();
		GridColumnGroup[] newArray = new GridColumnGroup[columnGroups.length];
		System.arraycopy(columnGroups, 0, newArray, 0, columnGroups.length);
		return newArray;
	}

	/**
	 * Returns the column group at the given, zero-relative index in the receiver. Throws an exception if the index is out of range.
	 * 
	 * @param index the index of the column group to return
	 * @return the column group at the given index
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridColumnGroup getColumnGroup(int index) {
		checkWidget();

		if (index < 0 || index >= columnGroups.length) SWT.error(SWT.ERROR_INVALID_RANGE);

		return columnGroups[index];
	}

	/**
	 * Sets the order that the items in the receiver should be displayed in to the given argument which is described in terms of the zero-relative ordering of when the items were added.
	 * 
	 * @param order the new order to display the items
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS -if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the item order is null</li>
	 *             <li>ERROR_INVALID_ARGUMENT - if the order is not the same length as the number of items, or if an item is listed twice, or if the order splits a column group</li>
	 *             </ul>
	 */
	public void setColumnOrder(int[] order) {
		checkWidget();

		if (order == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		if (order.length != displayOrderedColumns.size()) {
			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}

		boolean[] seen = new boolean[displayOrderedColumns.size()];

		for (int i = 0; i < order.length; i++) {
			if (order[i] < 0 || order[i] >= displayOrderedColumns.size()) {
				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
			}
			if (seen[order[i]]) {
				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
			}
			seen[order[i]] = true;
		}

		if (columnGroups.length != 0) {
			GridColumnGroup currentGroup = null;
			int colsInGroup = 0;

			for (int i = 0; i < order.length; i++) {
				GridColumn col = getColumn(order[i]);

				if (currentGroup != null) {
					if (col.getColumnGroup() != currentGroup && colsInGroup > 0) {
						SWT.error(SWT.ERROR_INVALID_ARGUMENT);
					} else {
						colsInGroup--;
						if (colsInGroup <= 0) {
							currentGroup = null;
						}
					}
				} else if (col.getColumnGroup() != null) {
					currentGroup = col.getColumnGroup();
					colsInGroup = currentGroup.getColumns().length - 1;
				}
			}
		}

		GridColumn[] cols = getColumns();

		displayOrderedColumns.clear();

		for (int i = 0; i < order.length; i++) {
			displayOrderedColumns.add(cols[order[i]]);
		}
	}

	/**
	 * Returns an array of {@code GridColumn}s which are the columns in the receiver. If no {@code GridColumn}s were created by the programmer, the array is empty, despite the fact that visually, one
	 * column of items may be visible. This occurs when the programmer uses the table like a list, adding items but never creating a column.
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its list of items, so modifying the array will not affect the receiver.
	 * </p>
	 * 
	 * @return the items in the receiver
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridColumn[] getColumns() {
		checkWidget();
		return (GridColumn[]) columns.toArray(new GridColumn[columns.size()]);
	}

	/**
	 * Returns the empty cell renderer.
	 * 
	 * @return Returns the emptyCellRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridCellRenderer getEmptyCellRenderer() {
		checkWidget();
		return emptyCellRenderer;
	}

	/**
	 * Returns the empty column header renderer.
	 * 
	 * @return Returns the emptyColumnHeaderRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IRenderer getEmptyColumnHeaderRenderer() {
		checkWidget();
		return emptyColumnHeaderRenderer;
	}

	/**
	 * Returns the empty column footer renderer.
	 * 
	 * @return Returns the emptyColumnFooterRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IRenderer getEmptyColumnFooterRenderer() {
		checkWidget();
		return emptyColumnFooterRenderer;
	}

	/**
	 * Returns the empty row header renderer.
	 * 
	 * @return Returns the emptyRowHeaderRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IRenderer getEmptyRowHeaderRenderer() {
		checkWidget();
		return emptyRowHeaderRenderer;
	}

	/**
	 * Returns the externally managed horizontal scrollbar.
	 * 
	 * @return the external horizontal scrollbar.
	 * @see #setHorizontalScrollBarProxy(IScrollBarProxy)
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IScrollBarProxy getHorizontalScrollBarProxy() {
		checkWidget();
		return hScroll;
	}

	/**
	 * Returns the externally managed vertical scrollbar.
	 * 
	 * @return the external vertical scrollbar.
	 * @see #setVerticalScrollBarProxy(IScrollBarProxy)
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IScrollBarProxy getVerticalScrollBarProxy() {
		checkWidget();
		return vScroll;
	}

	/**
	 * Gets the focus renderer.
	 * 
	 * @return Returns the focusRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IRenderer getFocusRenderer() {
		checkWidget();
		return focusRenderer;
	}

	/**
	 * Returns the height of the column headers. If this table has column groups, the returned value includes the height of group headers.
	 * 
	 * @return height of the column header row
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getHeaderHeight() {
		checkWidget();
		return headerHeight;
	}

	/**
	 * Returns the height of the column footers.
	 * 
	 * @return height of the column footer row
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getFooterHeight() {
		checkWidget();
		return footerHeight;
	}

	/**
	 * Returns the height of the column group headers.
	 * 
	 * @return height of column group headers
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getGroupHeaderHeight() {
		checkWidget();
		return groupHeaderHeight;
	}

	/**
	 * Returns {@code true} if the receiver's header is visible, and {@code false} otherwise.
	 * 
	 * @return the receiver's header's visibility state
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean getHeaderVisible() {
		checkWidget();
		return columnHeadersVisible;
	}

	/**
	 * Returns {@code true} if the receiver's footer is visible, and {@code false} otherwise
	 * 
	 * @return the receiver's footer's visibility state
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean getFooterVisible() {
		checkWidget();
		return columnFootersVisible;
	}

	/**
	 * Returns the item at the given, zero-relative index in the receiver. Throws an exception if the index is out of range.
	 * 
	 * @param index the index of the item to return
	 * @return the item at the given index
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> *
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem getItem(int index) {
		checkWidget();

		if (index < 0 || index >= items.size()) {
			SWT.error(SWT.ERROR_INVALID_RANGE);
		}

		return (GridItem) items.get(index);
	}

	/**
	 * Returns the item at the given point in the receiver or null if no such item exists. The point is in the coordinate system of the receiver.
	 * 
	 * @param point the point used to locate the item
	 * @return the item at the given point
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the point is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem getItem(Point point) {
		checkWidget();

		if (point == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		if (point.x < 0 || point.x > getClientArea().width) return null;

		Point p = new Point(point.x, point.y);

		int y2 = 0;

		if (columnHeadersVisible) {
			if (p.y <= headerHeight) {
				return null;
			}
			y2 += headerHeight;
		}

		GridItem itemToReturn = null;

		int row = getTopIndex();
		while (row < items.size() && y2 <= getClientArea().height) {
			GridItem currItem = (GridItem) items.get(row);
			if (currItem.isVisible()) {
				int currItemHeight = currItem.getHeight();

				if (p.y >= y2 && p.y < y2 + currItemHeight + 1) {
					itemToReturn = currItem;
					break;
				}

				y2 += currItemHeight + 1;
			}
			row++;
		}

		if (hasSpanning) {
			if (itemToReturn != null) {
				int itemIndex = this.getIndexOfItem(itemToReturn);

				GridColumn gridColumn = getColumn(itemToReturn, point);
				int displayColIndex = displayOrderedColumns.indexOf(gridColumn);

				// track back all previous columns and check their spanning
				for (int i = 0; i < itemIndex; i++) {
					GridItem gridItem = this.getItem(i);
					if (gridItem.isVisible() == false) {
						continue;
					}
					int span = gridItem.getRowSpan(displayColIndex);

					if (i + span >= itemIndex) {
						itemToReturn = gridItem;
						break;
					}
				}
			}
		}

		return itemToReturn;
	}

	/**
	 * Returns the number of items contained in the receiver.
	 * 
	 * @return the number of items
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getItemCount() {
		checkWidget();
		return getItems().length;
	}

	/**
	 * Returns the default height of the items in this <code>Grid</code>. See {@link #setItemHeight(int)} for details.
	 * 
	 * <p>
	 * IMPORTANT: The Grid's items need not all have the height returned by this method, because an item's height may have been changed by calling {@link GridItem#setHeight(int)}.
	 * 
	 * @return default height of items
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see #setItemHeight(int)
	 */
	public int getItemHeight() {
		checkWidget();
		return itemHeight;
	}

	/**
	 * Sets the default height for this <code>Grid</code>'s items. When this method is called, all existing items are resized to the specified height and items created afterwards will be initially
	 * sized to this height.
	 * <p>
	 * As long as no default height was set by the client through this method, the preferred height of the first item in this <code>Grid</code> is used as a default for all items (and is returned by
	 * {@link #getItemHeight()}).
	 * 
	 * @param height default height in pixels
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_ARGUMENT - if the height is < 1</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * 
	 * @see GridItem#getHeight()
	 * @see GridItem#setHeight(int)
	 */
	public void setItemHeight(int height) {
		checkWidget();
		if (height < 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		itemHeight = height;
		userModifiedItemHeight = true;
		for (int cnt = 0; cnt < items.size(); cnt++)
			((GridItem) items.get(cnt)).setHeight(height);
		hasDifferingHeights = false;
		setScrollValuesObsolete();
		redraw();
	}

	/**
	 * Returns true if the rows are resizable.
	 * 
	 * @return the row resizeable state
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see #setRowsResizeable(boolean)
	 */
	public boolean getRowsResizeable() {
		checkWidget();
		return rowsResizeable;
	}

	/**
	 * Sets the rows resizeable state of this <code>Grid</code>. The default is 'false'.
	 * <p>
	 * If a row in a <code>Grid</code> is resizeable, then the user can interactively change its height by dragging the border of the row header.
	 * <p>
	 * Note that for rows to be resizable the row headers must be visible.
	 * 
	 * @param rowsResizeable true if this <code>Grid</code>'s rows should be resizable
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see #setRowHeaderVisible(boolean)
	 */
	public void setRowsResizeable(boolean rowsResizeable) {
		checkWidget();
		this.rowsResizeable = rowsResizeable;
	}

	/**
	 * Returns a (possibly empty) array of {@code GridItem}s which are the items in the receiver.
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its list of items, so modifying the array will not affect the receiver.
	 * </p>
	 * 
	 * @return the items in the receiver
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem[] getItems() {
		checkWidget();
		return (GridItem[]) items.toArray(new GridItem[items.size()]);
	}

	/**
	 * 
	 * @param item
	 * @return t
	 */
	public int getIndexOfItem(GridItem item) {
		checkWidget();

		return items.indexOf(item);
	}

	/**
	 * Returns the line color.
	 * 
	 * @return Returns the lineColor.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public Color getLineColor() {
		checkWidget();
		return lineColor;
	}

	/**
	 * Returns true if the lines are visible.
	 * 
	 * @return Returns the linesVisible.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean getLinesVisible() {
		checkWidget();
		return linesVisible;
	}

	/**
	 * Returns true if the tree lines are visible.
	 * 
	 * @return Returns the treeLinesVisible.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean getTreeLinesVisible() {
		checkWidget();
		return treeLinesVisible;
	}

	/**
	 * Returns the next visible item in the table.
	 * 
	 * @param item item
	 * @return next visible item or null
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem getNextVisibleItem(GridItem item) {
		checkWidget();

		int index = items.indexOf(item);
		if (items.size() == index + 1) {
			return null;
		}

		GridItem nextItem = (GridItem) items.get(index + 1);

		while (!nextItem.isVisible()) {
			index++;
			if (items.size() == index + 1) {
				return null;
			}

			nextItem = (GridItem) items.get(index + 1);
		}

		return nextItem;
	}

	/**
	 * Returns the previous visible item in the table. Passing null for the item will return the last visible item in the table.
	 * 
	 * @param item item or null
	 * @return previous visible item or if item==null last visible item
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem getPreviousVisibleItem(GridItem item) {
		checkWidget();

		int index = 0;
		if (item == null) {
			index = items.size();
		} else {
			index = items.indexOf(item);
			if (index == 0) {
				return null;
			}
		}

		GridItem prevItem = (GridItem) items.get(index - 1);

		while (!prevItem.isVisible()) {
			index--;
			if (index == 0) {
				return null;
			}

			prevItem = (GridItem) items.get(index - 1);
		}

		return prevItem;
	}

	/**
	 * Returns the previous visible column in the table.
	 * 
	 * @param column column
	 * @return previous visible column or null
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridColumn getPreviousVisibleColumn(GridColumn column) {
		checkWidget();

		int index = displayOrderedColumns.indexOf(column);

		if (index == 0) return null;

		index--;

		GridColumn previous = (GridColumn) displayOrderedColumns.get(index);

		while (!previous.isVisible()) {
			if (index == 0) return null;

			index--;
			previous = (GridColumn) displayOrderedColumns.get(index);
		}

		return previous;
	}

	/**
	 * Returns the next visible column in the table.
	 * 
	 * @param column column
	 * @return next visible column or null
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridColumn getNextVisibleColumn(GridColumn column) {
		checkWidget();

		int index = displayOrderedColumns.indexOf(column);

		if (index == displayOrderedColumns.size() - 1) return null;

		index++;

		GridColumn next = (GridColumn) displayOrderedColumns.get(index);

		while (!next.isVisible()) {
			if (index == displayOrderedColumns.size() - 1) return null;

			index++;
			next = (GridColumn) displayOrderedColumns.get(index);
		}

		return next;
	}

	/**
	 * Returns the number of root items contained in the receiver.
	 * 
	 * @return the number of items
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getRootItemCount() {
		checkWidget();
		return rootItems.size();
	}

	/**
	 * Returns a (possibly empty) array of {@code GridItem}s which are the root items in the receiver.
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its list of items, so modifying the array will not affect the receiver.
	 * </p>
	 * 
	 * @return the root items in the receiver
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem[] getRootItems() {
		checkWidget();

		return (GridItem[]) rootItems.toArray(new GridItem[rootItems.size()]);
	}

	/**
	 * TODO: asl;fj
	 * 
	 * @param index
	 * @return asdf
	 */
	public GridItem getRootItem(int index) {
		checkWidget();

		if (index < 0 || index >= rootItems.size()) {
			SWT.error(SWT.ERROR_INVALID_RANGE);
		}

		return (GridItem) rootItems.get(index);
	}

	/**
	 * Gets the row header renderer.
	 * 
	 * @return Returns the rowHeaderRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IRenderer getRowHeaderRenderer() {
		checkWidget();
		return rowHeaderRenderer;
	}

	/**
	 * Returns a array of {@code GridItem}s that are currently selected in the receiver. The order of the items is unspecified. An empty array indicates that no items are selected.
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its selection, so modifying the array will not affect the receiver.
	 * <p>
	 * If cell selection is enabled, any items which contain at least one selected cell are returned.
	 * 
	 * @return an array representing the selection
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem[] getSelection() {
		checkWidget();

		if (!cellSelectionEnabled) {
			return (GridItem[]) selectedItems.toArray(new GridItem[selectedItems.size()]);
		} else {
			Vector items = new Vector();
			int itemCount = getItemCount();

			for (Iterator iter = selectedCells.iterator(); iter.hasNext();) {
				Point cell = (Point) iter.next();
				if (cell.y >= 0 && cell.y < itemCount) {
					GridItem item = getItem(cell.y);
					if (!items.contains(item)) items.add(item);
				}
			}
			return (GridItem[]) items.toArray(new GridItem[] {});
		}
	}

	/**
	 * Returns the number of selected items contained in the receiver. If cell selection is enabled, the number of items with at least one selected cell are returned.
	 * 
	 * @return the number of selected items
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getSelectionCount() {
		checkWidget();

		if (!cellSelectionEnabled) {
			return selectedItems.size();
		} else {
			Vector items = new Vector();
			for (Iterator iter = selectedCells.iterator(); iter.hasNext();) {
				Point cell = (Point) iter.next();
				GridItem item = getItem(cell.y);
				if (!items.contains(item)) items.add(item);
			}
			return items.size();
		}
	}

	/**
	 * Returns the number of selected cells contained in the receiver.
	 * 
	 * @return the number of selected cells
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getCellSelectionCount() {
		checkWidget();
		return selectedCells.size();
	}

	/**
	 * Returns the zero-relative index of the item which is currently selected in the receiver, or -1 if no item is selected. If cell selection is enabled, returns the index of first item that
	 * contains at least one selected cell.
	 * 
	 * @return the index of the selected item
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getSelectionIndex() {
		checkWidget();

		if (!cellSelectionEnabled) {
			if (selectedItems.size() == 0) {
				return -1;
			}

			return items.indexOf(selectedItems.get(0));
		} else {
			if (selectedCells.size() == 0) return -1;

			return ((Point) selectedCells.get(0)).y;
		}
	}

	/**
	 * Returns the zero-relative indices of the items which are currently selected in the receiver. The order of the indices is unspecified. The array is empty if no items are selected.
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its selection, so modifying the array will not affect the receiver.
	 * <p>
	 * If cell selection is enabled, returns the indices of any items which contain at least one selected cell.
	 * 
	 * @return the array of indices of the selected items
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int[] getSelectionIndices() {
		checkWidget();

		if (!cellSelectionEnabled) {
			int[] indices = new int[selectedItems.size()];
			int i = 0;
			for (Iterator itemIterator = selectedItems.iterator(); itemIterator.hasNext();) {
				GridItem item = (GridItem) itemIterator.next();
				indices[i] = items.indexOf(item);
				i++;
			}
			return indices;
		} else {
			Vector selectedRows = new Vector();
			for (Iterator iter = selectedCells.iterator(); iter.hasNext();) {
				Point cell = (Point) iter.next();
				GridItem item = getItem(cell.y);
				if (!selectedRows.contains(item)) selectedRows.add(item);
			}
			int[] indices = new int[selectedRows.size()];
			int i = 0;
			for (Iterator itemIterator = selectedRows.iterator(); itemIterator.hasNext();) {
				GridItem item = (GridItem) itemIterator.next();
				indices[i] = items.indexOf(item);
				i++;
			}
			return indices;
		}
	}

	/**
	 * Returns the zero-relative index of the item which is currently at the top of the receiver. This index can change when items are scrolled or new items are added or removed.
	 * 
	 * @return the index of the top item
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getTopIndex() {
		checkWidget();

		if (topIndex != -1) return topIndex;

		if (!vScroll.getVisible()) {
			topIndex = 0;
		} else {
			// figure out first visible row and last visible row
			int firstVisibleIndex = vScroll.getSelection();

			if (isTree) {
				Iterator itemsIter = items.iterator();
				int row = firstVisibleIndex + 1;

				while (row > 0 && itemsIter.hasNext()) {
					GridItem item = (GridItem) itemsIter.next();

					if (item.isVisible()) {
						row--;
						if (row == 0) {
							firstVisibleIndex = items.indexOf(item);
						}
					}
				}
			}

			topIndex = firstVisibleIndex;

			/* MOPR here lies more potential for increasing performance for the case (isTree || hasDifferingHeights) the topIndex could be derived from the previous value depending on a delta of the
			 * vScroll.getSelection() instead of being calculated completely anew */
		}

		return topIndex;
	}

	/**
	 * Returns the zero-relative index of the item which is currently at the bottom of the receiver. This index can change when items are scrolled, expanded or collapsed or new items are added or
	 * removed.
	 * <p>
	 * Note that the item with this index is often only partly visible; maybe only a single line of pixels is visible. Use {@link #isShown(GridItem)} to find out.
	 * <p>
	 * In extreme cases, getBottomIndex() may return the same value as {@link #getTopIndex()}.
	 * 
	 * @return the index of the bottom item
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	int getBottomIndex() {
		checkWidget();

		if (bottomIndex != -1) return bottomIndex;

		if (items.size() == 0) {
			bottomIndex = 0;
		} else if (getVisibleGridHeight() < 1) {
			bottomIndex = getTopIndex();
		} else {
			RowRange range = getRowRange(getTopIndex(), getVisibleGridHeight(), false, false);

			bottomIndex = range.endIndex;
			bottomIndexShownCompletely = range.height <= getVisibleGridHeight();
		}

		return bottomIndex;
	}

	/**
	 * Returns a {@link RowRange} ranging from the grid item at startIndex to that at endIndex.
	 * <p>
	 * This is primarily used to measure the height in pixel of such a range and to count the number of visible grid items within the range.
	 * 
	 * @param startIndex index of the first item in the range or -1 to the first visible item in this grid
	 * @param endIndex index of the last item in the range or -1 to use the last visible item in this grid
	 * @return
	 */
	private RowRange getRowRange(int startIndex, int endIndex) {

		// parameter preparation
		if (startIndex == -1) {
			// search frist visible item
			do
				startIndex++;
			while (startIndex < items.size() && !((GridItem) items.get(startIndex)).isVisible());
			if (startIndex == items.size()) return null;
		}
		if (endIndex == -1) {
			// search last visible item
			endIndex = items.size();
			do
				endIndex--;
			while (endIndex >= 0 && !((GridItem) items.get(endIndex)).isVisible());
			if (endIndex == -1) return null;
		}

		// fail fast
		if (startIndex < 0 || endIndex < 0 || startIndex >= items.size() || endIndex >= items.size() || endIndex < startIndex || ((GridItem) items.get(startIndex)).isVisible() == false || ((GridItem) items.get(endIndex)).isVisible() == false) SWT
				.error(SWT.ERROR_INVALID_ARGUMENT);
		RowRange range = new RowRange();
		range.startIndex = startIndex;
		range.endIndex = endIndex;

		if (isTree || hasDifferingHeights) {
			for (int idx = startIndex; idx <= endIndex; idx++) {
				GridItem currItem = (GridItem) items.get(idx);

				if (currItem.isVisible()) {
					if (range.rows > 0) range.height++; // height of horizontal row separator
					range.height += currItem.getHeight();
					range.rows++;
				}
			}
		} else {
			range.rows = range.endIndex - range.startIndex + 1;
			range.height = (getItemHeight() + 1) * range.rows - 1;
		}

		return range;
	}

	/**
	 * This method can be used to build a range of grid rows that is allowed to span a certain height in pixels.
	 * <p>
	 * It returns a {@link RowRange} that contains information about the range, especially the index of the last element in the range (or if inverse == true, then the index of the first element).
	 * <p>
	 * Note: Even if 'forceEndCompletelyInside' is set to true, the last item will not lie completely within the availableHeight, if (height of item at startIndex < availableHeight).
	 * 
	 * @param startIndex index of the first (if inverse==false) or last (if inverse==true) item in the range
	 * @param availableHeight height in pixels
	 * @param forceEndCompletelyInside if true, the last item in the range will lie completely within the availableHeight, otherwise it may lie partly outside this range
	 * @param inverse if true, then the first item in the range will be searched, not the last
	 * @return range of grid rows
	 * @see RowRange
	 */
	private RowRange getRowRange(int startIndex, int availableHeight, boolean forceEndCompletelyInside, boolean inverse) {
		// parameter preparation
		if (startIndex == -1) {
			if (!inverse) {
				// search frist visible item
				do
					startIndex++;
				while (startIndex < items.size() && !((GridItem) items.get(startIndex)).isVisible());
				if (startIndex == items.size()) return null;
			} else {
				// search last visible item
				startIndex = items.size();
				do
					startIndex--;
				while (startIndex >= 0 && !((GridItem) items.get(startIndex)).isVisible());
				if (startIndex == -1) return null;
			}
		}

		// fail fast
//		if (startIndex < 0 || startIndex >= items.size() || ((GridItem) items.get(startIndex)).isVisible() == false) SWT.error(SWT.ERROR_INVALID_ARGUMENT);

		RowRange range = new RowRange();

		if (availableHeight <= 0) {
			// special case: empty range
			range.startIndex = startIndex;
			range.endIndex = startIndex;
			range.rows = 0;
			range.height = 0;
			return range;
		}

		if (isTree || hasDifferingHeights) {
			int otherIndex = startIndex; // tentative end index
			int consumedItems = 0;
			int consumedHeight = 0;

			// consume height for startEnd (note: no separator pixel added here)
			consumedItems++;
			consumedHeight += ((GridItem) items.get(otherIndex)).getHeight();

			// note: we use "+2" in next line, because we only try to add another row if there
			// is room for the separator line + at least one pixel row for the additional item
			while (consumedHeight + 2 <= availableHeight) {
				// STEP 1:
				// try to find a visible item we can add

				int nextIndex = otherIndex;
				GridItem nextItem;

				do {
					if (!inverse) nextIndex++;
					else nextIndex--;

					if (nextIndex >= 0 && nextIndex < items.size()) nextItem = (GridItem) items.get(nextIndex);
					else nextItem = null;
				} while (nextItem != null && !nextItem.isVisible());

				if (nextItem == null) {
					// no visible item found
					break;
				}

				if (forceEndCompletelyInside) {
					// must lie completely within the allowed height
					if (!(consumedHeight + 1 + nextItem.getHeight() <= availableHeight)) break;
				}

				// we found one !!

				// STEP 2:
				// Consume height for this item

				consumedItems++;
				consumedHeight += 1; // height of separator line
				consumedHeight += nextItem.getHeight();

				// STEP 3:
				// make this item it the current guess for the other end
				otherIndex = nextIndex;
			}

			range.startIndex = !inverse ? startIndex : otherIndex;
			range.endIndex = !inverse ? otherIndex : startIndex;
			range.rows = consumedItems;
			range.height = consumedHeight;
		} else {
			int availableRows = (availableHeight + 1) / (getItemHeight() + 1);

			if (((getItemHeight() + 1) * range.rows - 1) + 1 < availableHeight) {
				// not all available space used yet
				// - so add another row if it need not be completely within availableHeight
				if (!forceEndCompletelyInside) availableRows++;
			}

			int otherIndex = startIndex + ((availableRows - 1) * (!inverse ? 1 : -1));
			if (otherIndex < 0) otherIndex = 0;
			if (otherIndex >= items.size()) otherIndex = items.size() - 1;

			range.startIndex = !inverse ? startIndex : otherIndex;
			range.endIndex = !inverse ? otherIndex : startIndex;
			range.rows = range.endIndex - range.startIndex + 1;
			range.height = (getItemHeight() + 1) * range.rows - 1;
		}

		return range;
	}

	/**
	 * Returns the height of the plain grid in pixels.
	 * <p>
	 * This includes all rows for visible items (i.e. items that return true on {@link GridItem#isVisible()} ; not only those currently visible on screen) and the 1 pixel separator between rows.
	 * <p>
	 * This does <em>not</em> include the height of the column headers.
	 * 
	 * @return height of plain grid
	 */
	int getGridHeight() {
		RowRange range = getRowRange(-1, -1);
		return range != null ? range.height : 0;
		/* MOPR currently this method is only used in #getTableSize() ; if it will be used for more important things in the future (e.g. the max value for vScroll.setValues() when doing pixel-by-pixel
		 * vertical scrolling) then this value should at least be cached or even updated incrementally when grid items are added/removed or expaned/collapsed (similar as #currentVisibleItems). (this
		 * is only necessary in the case (isTree || hasDifferingHeights)) */
	}

	/**
	 * Returns the height of the on-screen area that is available for showing the grid's rows, i.e. the client area of the scrollable minus the height of the column headers (if shown).
	 * 
	 * @return height of visible grid in pixels
	 */
	int getVisibleGridHeight() {
		return getClientArea().height - (columnHeadersVisible ? headerHeight : 0) - (columnFootersVisible ? footerHeight : 0);
	}

	/**
	 * Returns the height of the screen area that is available for showing the grid columns
	 * 
	 * @return
	 */
	int getVisibleGridWidth() {
		return getClientArea().width - (rowHeaderVisible ? rowHeaderWidth : 0);
	}

	/**
	 * Gets the top left renderer.
	 * 
	 * @return Returns the topLeftRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IRenderer getTopLeftRenderer() {
		checkWidget();
		return topLeftRenderer;
	}

	/**
	 * Gets the bottom left renderer.
	 * 
	 * @return Returns the bottomLeftRenderer.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public IRenderer getBottomLeftRenderer() {
		checkWidget();
		return bottomLeftRenderer;
	}

	/**
	 * Searches the receiver's list starting at the first column (index 0) until a column is found that is equal to the argument, and returns the index of that column. If no column is found, returns
	 * -1.
	 * 
	 * @param column the search column
	 * @return the index of the column
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the column is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int indexOf(GridColumn column) {
		checkWidget();

		if (column == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		if (column.getParent() != this) return -1;

		return columns.indexOf(column);
	}

	/**
	 * Searches the receiver's list starting at the first item (index 0) until an item is found that is equal to the argument, and returns the index of that item. If no item is found, returns -1.
	 * 
	 * @param item the search item
	 * @return the index of the item
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the item is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int indexOf(GridItem item) {
		checkWidget();

		if (item == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		if (item.getParent() != this) return -1;

		return items.indexOf(item);
	}

	/**
	 * Returns {@code true} if the receiver's row header is visible, and {@code false} otherwise.
	 * <p>
	 * 
	 * @return the receiver's row header's visibility state
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean isRowHeaderVisible() {
		checkWidget();
		return rowHeaderVisible;
	}

	/**
	 * Returns {@code true} if the item is selected, and {@code false} otherwise. Indices out of range are ignored. If cell selection is enabled, returns true if the item at the given index contains
	 * at least one selected cell.
	 * 
	 * @param index the index of the item
	 * @return the visibility state of the item at the index
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean isSelected(int index) {
		checkWidget();

		if (index < 0 || index >= items.size()) return false;

		if (!cellSelectionEnabled) {
			return isSelected((GridItem) items.get(index));
		} else {
			for (Iterator iter = selectedCells.iterator(); iter.hasNext();) {
				Point cell = (Point) iter.next();
				if (cell.y == index) return true;
			}
			return false;
		}
	}

	/**
	 * Returns true if the given item is selected. If cell selection is enabled, returns true if the given item contains at least one selected cell.
	 * 
	 * @param item item
	 * @return true if the item is selected.
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the item is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean isSelected(GridItem item) {
		checkWidget();
		if (!cellSelectionEnabled) {
			return selectedItems.contains(item);
		} else {
			int index = indexOf(item);
			if (index == -1) return false;
			for (Iterator iter = selectedCells.iterator(); iter.hasNext();) {
				Point cell = (Point) iter.next();
				if (cell.y == index) return true;
			}
			return false;
		}
	}

	/**
	 * Returns true if the given cell is selected.
	 * 
	 * @param cell cell
	 * @return true if the cell is selected.
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the cell is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean isCellSelected(Point cell) {
		checkWidget();

		if (cell == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		return selectedCells.contains(cell);
	}

	/**
	 * Removes the item from the receiver at the given zero-relative index.
	 * 
	 * @param index the index for the item
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void remove(int index) {
		checkWidget();
		if (index < 0 || index > items.size() - 1) {
			SWT.error(SWT.ERROR_INVALID_RANGE);
		}
		GridItem item = (GridItem) items.get(index);
		item.dispose();
		redraw();
	}

	/**
	 * Removes the items from the receiver which are between the given zero-relative start and end indices (inclusive).
	 * 
	 * @param start the start of the range
	 * @param end the end of the range
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_RANGE - if either the start or end are not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void remove(int start, int end) {
		checkWidget();

		for (int i = end; i >= start; i--) {
			if (i < 0 || i > items.size() - 1) {
				SWT.error(SWT.ERROR_INVALID_RANGE);
			}
			GridItem item = (GridItem) items.get(i);
			item.dispose();
		}
		redraw();
	}

	/**
	 * Removes the items from the receiver's list at the given zero-relative indices.
	 * 
	 * @param indices the array of indices of the items
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *             <li>ERROR_NULL_ARGUMENT - if the indices array is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void remove(int[] indices) {
		checkWidget();

		if (indices == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		GridItem[] removeThese = new GridItem[indices.length];
		for (int i = 0; i < indices.length; i++) {
			int j = indices[i];
			if (j < items.size() && j >= 0) {
				removeThese[i] = (GridItem) items.get(j);
			} else {
				SWT.error(SWT.ERROR_INVALID_RANGE);
			}

		}
		for (int i = 0; i < removeThese.length; i++) {
			GridItem item = removeThese[i];
			item.dispose();
		}
		redraw();
	}

	/**
	 * Removes all of the items from the receiver.
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void removeAll() {
		checkWidget();

		while (items.size() > 0) {
			((GridItem) items.get(0)).dispose();
		}
		deselectAll();
		redraw();
	}

	/**
	 * Removes the listener from the collection of listeners who will be notified when the receiver's selection changes.
	 * 
	 * @param listener the listener which should no longer be notified
	 * @see SelectionListener
	 * @see #addSelectionListener(SelectionListener)
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void removeSelectionListener(SelectionListener listener) {
		checkWidget();
		removeListener(SWT.Selection, listener);
		removeListener(SWT.DefaultSelection, listener);
	}

	/**
	 * Removes the listener from the collection of listeners who will be notified when the receiver's items changes.
	 * 
	 * @param listener the listener which should no longer be notified
	 * @see TreeListener
	 * @see #addTreeListener(TreeListener)
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void removeTreeListener(TreeListener listener) {
		checkWidget();
		removeListener(SWT.Expand, listener);
		removeListener(SWT.Collapse, listener);
	}

	/**
	 * Selects the item at the given zero-relative index in the receiver. If the item at the index was already selected, it remains selected. Indices that are out of range are ignored.
	 * <p>
	 * If cell selection is enabled, selects all cells at the given index.
	 * 
	 * @param index the index of the item to select
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void select(int index) {
		checkWidget();

		if (!selectionEnabled) return;

		if (index < 0 || index >= items.size()) return;

		GridItem item = (GridItem) items.get(index);

		if (!cellSelectionEnabled) {
			if (selectionType == SWT.MULTI && selectedItems.contains(item)) return;

			if (selectionType == SWT.SINGLE) selectedItems.clear();

			selectedItems.add(item);
		} else {
			selectCells(getCells(item));
		}

		redraw();
	}

	/**
	 * Selects the items in the range specified by the given zero-relative indices in the receiver. The range of indices is inclusive. The current selection is not cleared before the new items are
	 * selected.
	 * <p>
	 * If an item in the given range is not selected, it is selected. If an item in the given range was already selected, it remains selected. Indices that are out of range are ignored and no items
	 * will be selected if start is greater than end. If the receiver is single-select and there is more than one item in the given range, then all indices are ignored.
	 * <p>
	 * If cell selection is enabled, all cells within the given range are selected.
	 * 
	 * @param start the start of the range
	 * @param end the end of the range
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see Grid#setSelection(int,int)
	 */
	public void select(int start, int end) {
		checkWidget();

		if (!selectionEnabled) return;

		if (selectionType == SWT.SINGLE && start != end) return;

		if (!cellSelectionEnabled) {
			if (selectionType == SWT.SINGLE) selectedItems.clear();
		}

		for (int i = start; i <= end; i++) {
			if (i < 0) {
				continue;
			}
			if (i > items.size() - 1) {
				break;
			}

			GridItem item = (GridItem) items.get(i);

			if (!cellSelectionEnabled) {
				if (!selectedItems.contains(item)) selectedItems.add(item);
			} else {
				selectCells(getCells(item));
			}
		}

		redraw();
	}

	/**
	 * Selects the items at the given zero-relative indices in the receiver. The current selection is not cleared before the new items are selected.
	 * <p>
	 * If the item at a given index is not selected, it is selected. If the item at a given index was already selected, it remains selected. Indices that are out of range and duplicate indices are
	 * ignored. If the receiver is single-select and multiple indices are specified, then all indices are ignored.
	 * <p>
	 * If cell selection is enabled, all cells within the given indices are selected.
	 * 
	 * @param indices the array of indices for the items to select
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the array of indices is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see Grid#setSelection(int[])
	 */
	public void select(int[] indices) {
		checkWidget();

		if (indices == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		if (!selectionEnabled) return;

		if (selectionType == SWT.SINGLE && indices.length > 1) return;

		if (!cellSelectionEnabled) if (selectionType == SWT.SINGLE) selectedItems.clear();

		for (int i = 0; i < indices.length; i++) {
			int j = indices[i];

			if (j >= 0 && j < items.size()) {
				GridItem item = (GridItem) items.get(j);

				if (!cellSelectionEnabled) {
					if (!selectedItems.contains(item)) selectedItems.add(item);
				} else {
					selectCells(getCells(item));
				}
			}
		}
		redraw();
	}

	/**
	 * Selects all of the items in the receiver.
	 * <p>
	 * If the receiver is single-select, do nothing. If cell selection is enabled, all cells are selected.
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void selectAll() {
		checkWidget();

		if (!selectionEnabled) return;

		if (selectionType == SWT.SINGLE) return;

		if (cellSelectionEnabled) {
			selectAllCells();
			return;
		}

		selectedItems.clear();
		selectedItems.addAll(items);
		redraw();
	}

	/**
	 * Sets the empty cell renderer.
	 * 
	 * @param emptyCellRenderer The emptyCellRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setEmptyCellRenderer(GridCellRenderer emptyCellRenderer) {
		checkWidget();
		emptyCellRenderer.setDisplay(getDisplay());
		this.emptyCellRenderer = emptyCellRenderer;
	}

	/**
	 * Sets the empty column header renderer.
	 * 
	 * @param emptyColumnHeaderRenderer The emptyColumnHeaderRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setEmptyColumnHeaderRenderer(IRenderer emptyColumnHeaderRenderer) {
		checkWidget();
		emptyColumnHeaderRenderer.setDisplay(getDisplay());
		this.emptyColumnHeaderRenderer = emptyColumnHeaderRenderer;
	}

	/**
	 * Sets the empty column footer renderer.
	 * 
	 * @param emptyColumnFooterRenderer The emptyColumnFooterRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setEmptyColumnFooterRenderer(IRenderer emptyColumnFooterRenderer) {
		checkWidget();
		emptyColumnFooterRenderer.setDisplay(getDisplay());
		this.emptyColumnFooterRenderer = emptyColumnFooterRenderer;
	}

	/**
	 * Sets the empty row header renderer.
	 * 
	 * @param emptyRowHeaderRenderer The emptyRowHeaderRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setEmptyRowHeaderRenderer(IRenderer emptyRowHeaderRenderer) {
		checkWidget();
		emptyRowHeaderRenderer.setDisplay(getDisplay());
		this.emptyRowHeaderRenderer = emptyRowHeaderRenderer;
	}

	/**
	 * Sets the external horizontal scrollbar. Allows the scrolling to be managed externally from the table. This functionality is only intended when SWT.H_SCROLL is not given.
	 * <p>
	 * Using this feature, a ScrollBar could be instantiated outside the table, wrapped in IScrollBar and thus be 'connected' to the table.
	 * 
	 * @param scroll The horizontal scrollbar to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setHorizontalScrollBarProxy(IScrollBarProxy scroll) {
		checkWidget();
		if (getHorizontalBar() != null) {
			return;
		}
		hScroll = scroll;

		hScroll.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				onScrollSelection();
			}

			public void widgetDefaultSelected(SelectionEvent e) {}
		});
	}

	/**
	 * Sets the external vertical scrollbar. Allows the scrolling to be managed externally from the table. This functionality is only intended when SWT.V_SCROLL is not given.
	 * <p>
	 * Using this feature, a ScrollBar could be instantiated outside the table, wrapped in IScrollBar and thus be 'connected' to the table.
	 * 
	 * @param scroll The vertical scrollbar to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setVerticalScrollBarProxy(IScrollBarProxy scroll) {
		checkWidget();
		if (getVerticalBar() != null) {
			return;
		}
		vScroll = scroll;

		vScroll.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				onScrollSelection();
			}

			public void widgetDefaultSelected(SelectionEvent e) {}
		});
	}

	/**
	 * Sets the focus renderer.
	 * 
	 * @param focusRenderer The focusRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setFocusRenderer(IRenderer focusRenderer) {
		checkWidget();
		this.focusRenderer = focusRenderer;
	}

	/**
	 * Marks the receiver's header as visible if the argument is {@code true}, and marks it invisible otherwise.
	 * 
	 * @param show the new visibility state
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setHeaderVisible(boolean show) {
		checkWidget();
		this.columnHeadersVisible = show;
		redraw();
	}

	/**
	 * Marks the receiver's footer as visible if the argument is {@code true}, and marks it invisible otherwise.
	 * 
	 * @param show the new visibility state
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setFooterVisible(boolean show) {
		checkWidget();
		this.columnFootersVisible = show;
		redraw();
	}

	/**
	 * Sets the line color.
	 * 
	 * @param lineColor The lineColor to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setLineColor(Color lineColor) {
		checkWidget();
		this.lineColor = lineColor;
	}

	/**
	 * Sets the line visibility.
	 * 
	 * @param linesVisible Te linesVisible to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setLinesVisible(boolean linesVisible) {
		checkWidget();
		this.linesVisible = linesVisible;
		redraw();
	}

	/**
	 * Sets the tree line visibility.
	 * 
	 * @param treeLinesVisible
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setTreeLinesVisible(boolean treeLinesVisible) {
		checkWidget();
		this.treeLinesVisible = treeLinesVisible;
		redraw();
	}

	/**
	 * Sets the row header renderer.
	 * 
	 * @param rowHeaderRenderer The rowHeaderRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setRowHeaderRenderer(IRenderer rowHeaderRenderer) {
		checkWidget();
		rowHeaderRenderer.setDisplay(getDisplay());
		this.rowHeaderRenderer = rowHeaderRenderer;
	}

	/**
	 * Marks the receiver's row header as visible if the argument is {@code true}, and marks it invisible otherwise. When row headers are visible, horizontal scrolling is always done by column rather
	 * than by pixel.
	 * 
	 * @param show the new visibility state
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setRowHeaderVisible(boolean show) {
		checkWidget();
		this.rowHeaderVisible = show;
		setColumnScrolling(true);

		if (show && isAutoWidth()) {
			rowHeaderWidth = 1;

			for (Iterator iter = items.iterator(); iter.hasNext();) {
				GridItem iterItem = (GridItem) iter.next();
				rowHeaderWidth = Math.max(rowHeaderWidth, rowHeaderRenderer.computeSize(sizingGC, SWT.DEFAULT, SWT.DEFAULT, iterItem).x);
			}
		}

		redraw();
	}

	/**
	 * Selects the item at the given zero-relative index in the receiver. The current selection is first cleared, then the new item is selected.
	 * <p>
	 * If cell selection is enabled, all cells within the item at the given index are selected.
	 * 
	 * @param index the index of the item to select
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setSelection(int index) {
		checkWidget();

		if (!selectionEnabled) return;

		if (index >= 0 && index < items.size()) {
			if (!cellSelectionEnabled) {
				selectedItems.clear();
				selectedItems.add((GridItem) items.get(index));
				redraw();
			} else {
				selectedCells.clear();
				selectCells(getCells((GridItem) items.get(index)));
			}
		}
	}

	/**
	 * Selects the items in the range specified by the given zero-relative indices in the receiver. The range of indices is inclusive. The current selection is cleared before the new items are
	 * selected.
	 * <p>
	 * Indices that are out of range are ignored and no items will be selected if start is greater than end. If the receiver is single-select and there is more than one item in the given range, then
	 * all indices are ignored.
	 * <p>
	 * If cell selection is enabled, all cells within the given range are selected.
	 * 
	 * @param start the start index of the items to select
	 * @param end the end index of the items to select
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see Grid#deselectAll()
	 * @see Grid#select(int,int)
	 */
	public void setSelection(int start, int end) {
		checkWidget();

		if (!selectionEnabled) return;

		if (selectionType == SWT.SINGLE && start != end) return;

		if (!cellSelectionEnabled) {
			selectedItems.clear();
		} else {
			selectedCells.clear();
		}

		for (int i = start; i <= end; i++) {
			if (i < 0) {
				continue;
			}
			if (i > items.size() - 1) {
				break;
			}

			GridItem item = (GridItem) items.get(i);

			if (!cellSelectionEnabled) {
				selectedItems.add(item);
			} else {
				selectCells(getCells(item));
			}
		}
		redraw();
	}

	/**
	 * Selects the items at the given zero-relative indices in the receiver. The current selection is cleared before the new items are selected.
	 * <p>
	 * Indices that are out of range and duplicate indices are ignored. If the receiver is single-select and multiple indices are specified, then all indices are ignored.
	 * <p>
	 * If cell selection is enabled, all cells within the given indices are selected.
	 * 
	 * @param indices the indices of the items to select
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the array of indices is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see Grid#deselectAll()
	 * @see Grid#select(int[])
	 */
	public void setSelection(int[] indices) {
		checkWidget();

		if (!selectionEnabled) return;

		if (selectionType == SWT.SINGLE && indices.length > 1) return;

		if (!cellSelectionEnabled) {
			selectedItems.clear();
		} else {
			selectedCells.clear();
		}

		for (int i = 0; i < indices.length; i++) {
			int j = indices[i];

			if (j < 0) {
				continue;
			}
			if (j > items.size() - 1) {
				break;
			}

			GridItem item = (GridItem) items.get(j);

			if (!cellSelectionEnabled) {
				selectedItems.add(item);
			} else {
				selectCells(getCells(item));
			}
		}
		redraw();
	}

	/**
	 * Sets the receiver's selection to be the given array of items. The current selection is cleared before the new items are selected.
	 * <p>
	 * Items that are not in the receiver are ignored. If the receiver is single-select and multiple items are specified, then all items are ignored. If cell selection is enabled, all cells within the
	 * given items are selected.
	 * 
	 * @param _items the array of items
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the array of items is null</li> <li>ERROR_INVALID_ARGUMENT - if one of the items has been disposed</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 * @see Grid#deselectAll()
	 * @see Grid#select(int[])
	 * @see Grid#setSelection(int[])
	 */
	public void setSelection(GridItem[] _items) {
		checkWidget();

		if (!selectionEnabled) return;

		if (_items == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		if (selectionType == SWT.SINGLE && _items.length > 1) return;

		if (!cellSelectionEnabled) {
			selectedItems.clear();
		} else {
			selectedCells.clear();
		}

		for (int i = 0; i < _items.length; i++) {
			GridItem item = _items[i];
			if (item == null) continue;
			if (item.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
			if (item.getParent() != this) continue;

			if (!cellSelectionEnabled) {
				selectedItems.add(item);
			} else {
				selectCells(getCells(item));
			}
		}

		redraw();
	}

	/**
	 * Sets the zero-relative index of the item which is currently at the top of the receiver. This index can change when items are scrolled or new items are added and removed.
	 * 
	 * @param index the index of the top item
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setTopIndex(int index) {
		checkWidget();
		if (index < 0 || index >= items.size()) {
			return;
		}

		GridItem item = (GridItem) items.get(index);
		if (!item.isVisible()) {
			return;
		}

		if (getVerticalBar() != null) {
			int vScrollAmount = 0;

			for (int i = 0; i < index; i++) {
				if (((GridItem) items.get(i)).isVisible()) {
					vScrollAmount++;
				}
			}

			vScroll.setSelection(vScrollAmount);
			topIndex = -1;
			bottomIndex = -1;
		} else {
			int vScrollAmount = 0;

			for (int i = 0; i < index; i++) {
				if (((GridItem) items.get(i)).isVisible()) {
					vScrollAmount++;
				}
			}

			topIndex = vScrollAmount;
		}

		redraw();
	}

	/**
	 * Sets the top left renderer.
	 * 
	 * @param topLeftRenderer The topLeftRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setTopLeftRenderer(IRenderer topLeftRenderer) {
		checkWidget();
		topLeftRenderer.setDisplay(getDisplay());
		this.topLeftRenderer = topLeftRenderer;
	}

	/**
	 * Sets the bottom left renderer.
	 * 
	 * @param bottomLeftRenderer The topLeftRenderer to set.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setBottomLeftRenderer(IRenderer bottomLeftRenderer) {
		checkWidget();
		bottomLeftRenderer.setDisplay(getDisplay());
		this.bottomLeftRenderer = bottomLeftRenderer;
	}

	/**
	 * Shows the column. If the column is already showing in the receiver, this method simply returns. Otherwise, the columns are scrolled until the column is visible.
	 * 
	 * @param col the column to be shown
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void showColumn(GridColumn col) {
		checkWidget();

		if (!col.isVisible()) {
			GridColumnGroup group = col.getColumnGroup();
			group.setExpanded(!group.getExpanded());
			if (group.getExpanded()) {
				group.notifyListeners(SWT.Expand, new Event());
			} else {
				group.notifyListeners(SWT.Collapse, new Event());
			}
		}

		if (!hScroll.getVisible()) {
			return;
		}

		int x = getColumnHeaderXPosition(col);

		int firstVisibleX = 0;
		if (rowHeaderVisible) {
			firstVisibleX = rowHeaderWidth;
		}

		// if its visible just return
		if (x >= firstVisibleX && (x + col.getWidth()) <= (firstVisibleX + (getClientArea().width - firstVisibleX))) {
			return;
		}

		if (!getColumnScrolling()) {
			if (x < firstVisibleX) {
				hScroll.setSelection(getHScrollSelectionInPixels() - (firstVisibleX - x));
			} else {
				if (col.getWidth() > getClientArea().width - firstVisibleX) {
					hScroll.setSelection(getHScrollSelectionInPixels() + (x - firstVisibleX));
				} else {
					x -= getClientArea().width - firstVisibleX - col.getWidth();
					hScroll.setSelection(getHScrollSelectionInPixels() + (x - firstVisibleX));
				}
			}
		} else {
			if (x < firstVisibleX || col.getWidth() > getClientArea().width - firstVisibleX) {
				int sel = displayOrderedColumns.indexOf(col);
				hScroll.setSelection(sel);
			} else {
				int availableWidth = getClientArea().width - firstVisibleX - col.getWidth();

				GridColumn prevCol = getPreviousVisibleColumn(col);
				GridColumn currentScrollTo = col;

				while (true) {
					if (prevCol == null || prevCol.getWidth() > availableWidth) {
						int sel = displayOrderedColumns.indexOf(currentScrollTo);
						hScroll.setSelection(sel);
						break;
					} else {
						availableWidth -= prevCol.getWidth();
						currentScrollTo = prevCol;
						prevCol = getPreviousVisibleColumn(prevCol);
					}
				}
			}
		}

		redraw();
	}

	/**
	 * Returns true if 'item' is currently being <em>completely</em> shown in this <code>Grid</code>'s visible on-screen area.
	 * 
	 * <p>
	 * Here, "completely" only refers to the item's height, not its width. This means this method returns true also if some cells are horizontally scrolled away.
	 * 
	 * @param item
	 * @return true if 'item' is shown
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> <li>
	 *             ERROR_INVALID_ARGUMENT - if 'item' is not contained in the receiver</li>
	 *             </ul>
	 */
	boolean isShown(GridItem item) {
		checkWidget();

		if (!item.isVisible()) return false;

		int itemIndex = items.indexOf(item);

		if (itemIndex == -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);

		int firstVisibleIndex = getTopIndex();
		int lastVisibleIndex = getBottomIndex();

		return (itemIndex >= firstVisibleIndex && itemIndex < lastVisibleIndex) || (itemIndex == lastVisibleIndex && bottomIndexShownCompletely);
	}

	/**
	 * Shows the item. If the item is already showing in the receiver, this method simply returns. Otherwise, the items are scrolled until the item is visible.
	 * 
	 * @param item the item to be shown
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             <li>ERROR_INVALID_ARGUMENT - if 'item' is not contained in the receiver</li>
	 *             </ul>
	 */
	public void showItem(GridItem item) {
		checkWidget();

		updateScrollbars();

		// if no items are visible on screen then abort
		if (getVisibleGridHeight() < 1) {
			return;
		}

		// if its visible just return
		if (isShown(item)) {
			return;
		}

		if (!item.isVisible()) {
			GridItem parent = item.getParentItem();
			do {
				if (!parent.isExpanded()) {
					parent.setExpanded(true);
					parent.fireEvent(SWT.Expand);
				}
				parent = parent.getParentItem();
			} while (parent != null);
		}

		int newTopIndex = items.indexOf(item);

		if (newTopIndex >= getBottomIndex()) {
			RowRange range = getRowRange(newTopIndex, getVisibleGridHeight(), true, true); // note: inverse==true
			newTopIndex = range.startIndex; // note: use startIndex because of inverse==true
		}

		setTopIndex(newTopIndex);
	}

	/**
	 * Shows the selection. If the selection is already showing in the receiver, this method simply returns. Otherwise, the items are scrolled until the selection is visible.
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void showSelection() {
		checkWidget();

		if (scrollValuesObsolete) updateScrollbars();

		GridItem item = null;

		if (!cellSelectionEnabled) {
			if (selectedItems.size() == 0) {
				return;
			}

			item = (GridItem) selectedItems.get(0);
			showItem(item);
		} else {
			if (selectedCells.size() == 0) return;

			Point cell = (Point) selectedCells.get(0);
			item = getItem(cell.y);
			showItem(item);
			GridColumn col = getColumn(cell.x);
			showColumn(col);
		}

	}

	/**
	 * Enables selection highlighting if the argument is <code>true</code>.
	 * 
	 * @param selectionEnabled the selection enabled state
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setSelectionEnabled(boolean selectionEnabled) {
		checkWidget();

		if (!selectionEnabled) {
			selectedItems.clear();
			redraw();
		}

		this.selectionEnabled = selectionEnabled;
	}

	/**
	 * Returns <code>true</code> if selection is enabled, false otherwise.
	 * 
	 * @return the selection enabled state
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean getSelectionEnabled() {
		checkWidget();
		return selectionEnabled;
	}

	/**
	 * Computes and sets the height of the header row. This method will ask for the preferred size of all the column headers and use the max.
	 * 
	 * @param gc GC for font metrics, etc.
	 */
	private void computeHeaderHeight(GC gc) {

		int colHeaderHeight = 0;
		for (Iterator columnsIterator = columns.iterator(); columnsIterator.hasNext();) {
			GridColumn column = (GridColumn) columnsIterator.next();
			colHeaderHeight = Math.max(column.getHeaderRenderer().computeSize(gc, column.getWidth(), SWT.DEFAULT, column).y, colHeaderHeight);
		}

		int groupHeight = 0;
		for (int groupIndex = 0; groupIndex < columnGroups.length; groupIndex++) {
			GridColumnGroup group = (GridColumnGroup) columnGroups[groupIndex];
			groupHeight = Math.max(group.getHeaderRenderer().computeSize(gc, SWT.DEFAULT, SWT.DEFAULT, group).y, groupHeight);
		}

		headerHeight = colHeaderHeight + groupHeight;
		groupHeaderHeight = groupHeight;
	}

	private void computeFooterHeight(GC gc) {

		int colFooterHeight = 0;
		for (Iterator columnsIterator = columns.iterator(); columnsIterator.hasNext();) {
			GridColumn column = (GridColumn) columnsIterator.next();
			colFooterHeight = Math.max(column.getFooterRenderer().computeSize(gc, column.getWidth(), SWT.DEFAULT, column).y, colFooterHeight);
		}

		footerHeight = colFooterHeight;
	}

	/**
	 * Returns the computed default item height. Currently this method just gets the preferred size of all the cells in the given row and returns that (it is then used as the height of all rows with
	 * items having a height of -1).
	 * 
	 * @param item item to use for sizing
	 * @param gc GC used to perform font metrics,etc.
	 * @return the row height
	 */
	private int computeItemHeight(GridItem item, GC gc) {
		int height = 1;

		if (columns.size() == 0 || items.size() == 0) {
			return height;
		}

		for (Iterator columnsIterator = columns.iterator(); columnsIterator.hasNext();) {
			GridColumn column = (GridColumn) columnsIterator.next();
			column.getCellRenderer().setColumn(indexOf(column));
			height = Math.max(height, column.getCellRenderer().computeSize(gc, SWT.DEFAULT, SWT.DEFAULT, item).y);
		}

		if (rowHeaderVisible && rowHeaderRenderer != null) {
			height = Math.max(height, rowHeaderRenderer.computeSize(gc, SWT.DEFAULT, SWT.DEFAULT, item).y);
		}

		return height <= 0 ? 16 : height;
	}

	/**
	 * Returns the x position of the given column. Takes into account scroll position.
	 * 
	 * @param column given column
	 * @return x position
	 */
	private int getColumnHeaderXPosition(GridColumn column) {
		if (!column.isVisible()) {
			return -1;
		}

		int x = 0;

		x -= getHScrollSelectionInPixels();

		if (rowHeaderVisible) {
			x += rowHeaderWidth;
		}
		for (Iterator column2Iterator = displayOrderedColumns.iterator(); column2Iterator.hasNext();) {
			GridColumn column2 = (GridColumn) column2Iterator.next();

			if (!column2.isVisible()) {
				continue;
			}

			if (column2 == column) {
				break;
			}

			x += column2.getWidth();
		}

		return x;
	}

	/**
	 * Returns the hscroll selection in pixels. This method abstracts away the differences between column by column scrolling and pixel based scrolling.
	 * 
	 * @return the horizontal scroll selection in pixels
	 */
	private int getHScrollSelectionInPixels() {
		int selection = hScroll.getSelection();
		if (columnScrolling) {
			int pixels = 0;
			for (int i = 0; i < selection; i++) {
				pixels += ((GridColumn) displayOrderedColumns.get(i)).getWidth();
			}
			selection = pixels;
		}
		return selection;
	}

	/**
	 * Returns the size of the preferred size of the inner table.
	 * 
	 * @return the preferred size of the table.
	 */
	private Point getTableSize() {
		int x = 0;
		int y = 0;

		if (columnHeadersVisible) {
			y += headerHeight;
		}

		if (columnFootersVisible) {
			y += footerHeight;
		}

		y += getGridHeight();

		if (rowHeaderVisible) {
			x += rowHeaderWidth;
		}

		for (Iterator columnIterator = columns.iterator(); columnIterator.hasNext();) {
			GridColumn column = (GridColumn) columnIterator.next();
			if (column.isVisible()) {
				x += column.getWidth();
			}
		}

		return new Point(x, y);
	}

	/**
	 * Manages the header column dragging and calculates the drop point, triggers a redraw.
	 * 
	 * @param x mouse x
	 * @return true if this event has been consumed.
	 */
	private boolean handleColumnDragging(int x) {

		GridColumn local_dragDropBeforeColumn = null;
		GridColumn local_dragDropAfterColumn = null;

		int x2 = 1;

		if (rowHeaderVisible) {
			x2 += rowHeaderWidth + 1;
		}

		x2 -= getHScrollSelectionInPixels();

		@SuppressWarnings("unused")
		int i = 0;
		GridColumn previousVisibleCol = null;
		boolean nextVisibleColumnIsBeforeCol = false;
		GridColumn firstVisibleCol = null;
		GridColumn lastVisibleCol = null;

		if (x < x2) {
			for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
				GridColumn column = (GridColumn) columnIterator.next();
				if (!column.isVisible()) {
					continue;
				}
				local_dragDropBeforeColumn = column;
				break;
			}
			local_dragDropAfterColumn = null;
		} else {
			for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
				GridColumn column = (GridColumn) columnIterator.next();
				if (!column.isVisible()) {
					continue;
				}

				i++;

				if (firstVisibleCol == null) {
					firstVisibleCol = column;
				}
				lastVisibleCol = column;

				if (nextVisibleColumnIsBeforeCol) {
					local_dragDropBeforeColumn = column;
					nextVisibleColumnIsBeforeCol = false;
				}

				if (x >= x2 && x <= (x2 + column.getWidth())) {
					if (x <= (x2 + column.getWidth() / 2)) {
						local_dragDropBeforeColumn = column;
						local_dragDropAfterColumn = previousVisibleCol;
					} else {
						local_dragDropAfterColumn = column;

						// the next visible column is the before col
						nextVisibleColumnIsBeforeCol = true;
					}
				}

				x2 += column.getWidth();
				previousVisibleCol = column;
			}

			if (local_dragDropBeforeColumn == null) {
				local_dragDropAfterColumn = lastVisibleCol;
			}
		}

		currentHeaderDragX = x;

		if (local_dragDropBeforeColumn != dragDropBeforeColumn || (dragDropBeforeColumn == null && dragDropAfterColumn == null)) {
			dragDropPointValid = true;

			// Determine if valid drop point
			if (columnGroups.length != 0) {

				if (columnBeingPushed.getColumnGroup() == null) {
					if (local_dragDropBeforeColumn != null && local_dragDropAfterColumn != null && local_dragDropBeforeColumn.getColumnGroup() != null && local_dragDropBeforeColumn.getColumnGroup() == local_dragDropAfterColumn.getColumnGroup()) {
						// Dont move a column w/o a group in between two columns
						// in the same group
						dragDropPointValid = false;
					}
				} else {
					if (!(local_dragDropBeforeColumn != null && local_dragDropBeforeColumn.getColumnGroup() == columnBeingPushed.getColumnGroup())
							&& !(local_dragDropAfterColumn != null && local_dragDropAfterColumn.getColumnGroup() == columnBeingPushed.getColumnGroup())) {
						// Dont move a column with a group
						dragDropPointValid = false;
					}
				}
			} else {
				dragDropPointValid = true;
			}
		}

		dragDropBeforeColumn = local_dragDropBeforeColumn;
		dragDropAfterColumn = local_dragDropAfterColumn;

		Rectangle clientArea = getClientArea();
		redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

		return true;
	}

	/**
	 * Handles the moving of columns after a column is dropped.
	 */
	private void handleColumnDrop() {
		draggingColumn = false;

		if ((dragDropBeforeColumn != columnBeingPushed && dragDropAfterColumn != columnBeingPushed) && (columnGroups.length == 0 || dragDropPointValid)) {

			int notifyFrom = displayOrderedColumns.indexOf(columnBeingPushed);
			int notifyTo = notifyFrom;

			displayOrderedColumns.remove(columnBeingPushed);

			if (dragDropBeforeColumn == null) {

				notifyTo = displayOrderedColumns.size();
				displayOrderedColumns.add(columnBeingPushed);
			} else if (dragDropAfterColumn == null) {
				displayOrderedColumns.add(0, columnBeingPushed);
				notifyFrom = 0;
			} else {
				int insertAtIndex = 0;

				if (columnGroups.length != 0) {
					// ensure that we aren't putting this column into a group,
					// this is possible if
					// there are invisible columns between the after and before
					// cols

					if (dragDropBeforeColumn.getColumnGroup() == columnBeingPushed.getColumnGroup()) {
						insertAtIndex = displayOrderedColumns.indexOf(dragDropBeforeColumn);
					} else if (dragDropAfterColumn.getColumnGroup() == columnBeingPushed.getColumnGroup()) {
						insertAtIndex = displayOrderedColumns.indexOf(dragDropAfterColumn) + 1;
					} else {
						if (dragDropBeforeColumn.getColumnGroup() == null) {
							insertAtIndex = displayOrderedColumns.indexOf(dragDropBeforeColumn);
						} else {
							GridColumnGroup beforeGroup = dragDropBeforeColumn.getColumnGroup();
							insertAtIndex = displayOrderedColumns.indexOf(dragDropBeforeColumn);
							while (insertAtIndex > 0 && ((GridColumn) displayOrderedColumns.get(insertAtIndex - 1)).getColumnGroup() == beforeGroup) {
								insertAtIndex--;
							}

						}
					}
				} else {
					insertAtIndex = displayOrderedColumns.indexOf(dragDropBeforeColumn);
				}
				displayOrderedColumns.add(insertAtIndex, columnBeingPushed);
				notifyFrom = Math.min(notifyFrom, insertAtIndex);
				notifyTo = Math.max(notifyTo, insertAtIndex);
			}

			for (int i = notifyFrom; i <= notifyTo; i++) {
				((GridColumn) displayOrderedColumns.get(i)).fireMoved();
			}
		}

		redraw();
	}

	/**
	 * Determines if the mouse is pushing the header but has since move out of the header bounds and therefore should be drawn unpushed. Also initiates a column header drag when appropriate.
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return true if this event has been consumed.
	 */
	private boolean handleColumnHeaderHoverWhilePushing(int x, int y) {
		GridColumn overThis = overColumnHeader(x, y);

		if ((overThis == columnBeingPushed) != pushingAndHovering) {
			pushingAndHovering = (overThis == columnBeingPushed);
			redraw();
		}
		if (columnBeingPushed.getMoveable()) {

			if (pushingAndHovering && Math.abs(startHeaderPushX - x) > 3) {

				// stop pushing
				pushingColumn = false;
				columnBeingPushed.getHeaderRenderer().setMouseDown(false);
				columnBeingPushed.getHeaderRenderer().setHover(false);

				// now dragging
				draggingColumn = true;
				columnBeingPushed.getHeaderRenderer().setMouseDown(false);

				startHeaderDragX = x;

				dragDropAfterColumn = null;
				dragDropBeforeColumn = null;
				dragDropPointValid = true;

				handleColumnDragging(x);
			}
		}

		return true;
	}

	/**
	 * Determines if a column group header has been clicked and forwards the event to the header renderer.
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return true if this event has been consumed.
	 */
	private boolean handleColumnGroupHeaderClick(int x, int y) {

		if (!columnHeadersVisible) {
			return false;
		}

		GridColumnGroup overThis = overColumnGroupHeader(x, y);

		if (overThis == null) {
			return false;
		}

		int headerX = 0;
		if (rowHeaderVisible) {
			headerX += rowHeaderWidth;
		}

		int width = 0;
		boolean firstCol = false;

		for (Iterator colIterator = displayOrderedColumns.iterator(); colIterator.hasNext();) {
			GridColumn col = (GridColumn) colIterator.next();
			if (col.getColumnGroup() == overThis && col.isVisible()) {
				firstCol = true;
				width += col.getWidth();
			}
			if (!firstCol && col.isVisible()) {
				headerX += col.getWidth();
			}
		}

		overThis.getHeaderRenderer().setBounds(headerX - getHScrollSelectionInPixels(), 0, width, groupHeaderHeight);
		return overThis.getHeaderRenderer().notify(IInternalWidget.LeftMouseButtonDown, new Point(x, y), overThis);
	}

	/**
	 * Determines if a column header has been clicked, updates the renderer state and triggers a redraw if necesary.
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return true if this event has been consumed.
	 */
	private boolean handleColumnHeaderPush(int x, int y) {
		if (!columnHeadersVisible) {
			return false;
		}

		GridColumn overThis = overColumnHeader(x, y);

		if (overThis == null) {
			return false;
		}

		columnBeingPushed = overThis;

		// draw pushed
		columnBeingPushed.getHeaderRenderer().setMouseDown(true);
		columnBeingPushed.getHeaderRenderer().setHover(true);
		pushingAndHovering = true;
		redraw();

		startHeaderPushX = x;
		pushingColumn = true;

		setCapture(true);

		return true;
	}

	private boolean handleColumnFooterPush(int x, int y) {
		if (!columnFootersVisible) {
			return false;
		}

		GridColumn overThis = overColumnFooter(x, y);

		if (overThis == null) {
			return false;
		}

		return true;
	}

	/**
	 * Sets the new width of the column being resized and fires the appropriate listeners.
	 * 
	 * @param x mouse x
	 */
	private void handleColumnResizerDragging(int x) {
		int newWidth = resizingColumnStartWidth + (x - resizingStartX);
		if (newWidth < MIN_COLUMN_HEADER_WIDTH) {
			newWidth = MIN_COLUMN_HEADER_WIDTH;
		}

		if (columnScrolling) {
			int maxWidth = getClientArea().width;
			if (rowHeaderVisible) maxWidth -= rowHeaderWidth;
			if (newWidth > maxWidth) newWidth = maxWidth;
		}

		if (newWidth == columnBeingResized.getWidth()) {
			return;
		}

		columnBeingResized.setWidth(newWidth, false);
		scrollValuesObsolete = true;

		Rectangle clientArea = getClientArea();
		redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

		columnBeingResized.fireResized();

		for (int index = displayOrderedColumns.indexOf(columnBeingResized) + 1; index < displayOrderedColumns.size(); index++) {
			GridColumn col = (GridColumn) displayOrderedColumns.get(index);
			if (col.isVisible()) col.fireMoved();
		}
	}

	/**
	 * Sets the new height of the item of the row being resized and fires the appropriate listeners.
	 * 
	 * @param x mouse x
	 */
	private void handleRowResizerDragging(int y) {
		int newHeight = resizingRowStartHeight + (y - resizingStartY);
		if (newHeight < MIN_ROW_HEADER_HEIGHT) {
			newHeight = MIN_ROW_HEADER_HEIGHT;
		}

		if (newHeight > getClientArea().height) {
			newHeight = getClientArea().height;
		}

		if (newHeight == rowBeingResized.getHeight()) {
			return;
		}

		Event e = new Event();
		e.item = rowBeingResized;
		e.widget = this;
		e.detail = newHeight;

		rowBeingResized.notifyListeners(SWT.Resize, e);

		if (e.doit == false) return;

		newHeight = e.detail;

		if (newHeight < MIN_ROW_HEADER_HEIGHT) {
			newHeight = MIN_ROW_HEADER_HEIGHT;
		}

		if (newHeight > getClientArea().height) {
			newHeight = getClientArea().height;
		}

		rowBeingResized.setHeight(newHeight);
		scrollValuesObsolete = true;

		Rectangle clientArea = getClientArea();
		redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);
	}

	/**
	 * Determines if the mouse is hovering on a column resizer and changes the pointer and sets field appropriately.
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return true if this event has been consumed.
	 */
	private boolean handleHoverOnColumnResizer(int x, int y) {
		boolean over = false;
		if (y <= headerHeight) {
			int x2 = 0;

			if (rowHeaderVisible) {
				x2 += rowHeaderWidth;
			}

			x2 -= getHScrollSelectionInPixels();

			for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
				GridColumn column = (GridColumn) columnIterator.next();
				if (!column.isVisible()) {
					continue;
				}
				x2 += column.getWidth();

				if (x2 >= (x - COLUMN_RESIZER_THRESHOLD) && x2 <= (x + COLUMN_RESIZER_THRESHOLD)) {
					if (column.getResizeable()) {
						if (column.getColumnGroup() != null && y <= groupHeaderHeight) {
							// if this is not the last column
							if (column != column.getColumnGroup().getLastVisibleColumn()) {
								break;
							}
						}

						over = true;
						columnBeingResized = column;
					}
					break;
				}
			}
		}

		if (over != hoveringOnColumnResizer) {
			if (over) {
				setCursor(getDisplay().getSystemCursor(SWT.CURSOR_SIZEWE));
			} else {
				columnBeingResized = null;
				setCursor(null);
			}
			hoveringOnColumnResizer = over;
		}
		return over;
	}

	/**
	 * Determines if the mouse is hovering on a row resizer and changes the pointer and sets field appropriately.
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return true if this event has been consumed.
	 */
	private boolean handleHoverOnRowResizer(int x, int y) {
		rowBeingResized = null;
		boolean over = false;
		if (x <= rowHeaderWidth) {
			int y2 = 0;

			if (columnHeadersVisible) {
				y2 += headerHeight;
			}

			int row = getTopIndex();
			while (row < items.size() && y2 <= getClientArea().height) {
				GridItem currItem = (GridItem) items.get(row);
				if (currItem.isVisible()) {
					y2 += currItem.getHeight() + 1;

					if (y2 >= (y - ROW_RESIZER_THRESHOLD) && y2 <= (y + ROW_RESIZER_THRESHOLD)) {
						// if (currItem.isResizeable())
						{
							over = true;
							rowBeingResized = currItem;
						}
						// do not brake here, because in case of overlapping
						// row resizers we need to find the last one
					} else {
						if (rowBeingResized != null) {
							// we have passed all (overlapping) row resizers, so break
							break;
						}
					}
				}
				row++;
			}
		}

		if (over != hoveringOnRowResizer) {
			if (over) {
				setCursor(getDisplay().getSystemCursor(SWT.CURSOR_SIZENS));
			} else {
				rowBeingResized = null;
				setCursor(null);
			}
			hoveringOnRowResizer = over;
		}
		return over;
	}

	/**
	 * Returns the cell at the given point in the receiver or null if no such cell exists. The point is in the coordinate system of the receiver.
	 * 
	 * @param point the point used to locate the item
	 * @return the cell at the given point
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the point is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public Point getCell(Point point) {
		checkWidget();

		if (point == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		if (point.x < 0 || point.x > getClientArea().width) return null;

		GridItem item = getItem(point);
		GridColumn column = getColumn(point);

		if (item != null && column != null) {
			return new Point(columns.indexOf(column), items.indexOf(item));
		} else {
			return null;
		}
	}

	/**
	 * Paints.
	 * 
	 * @param e paint event
	 */
	private void onPaint(PaintEvent e) {
		int insertMarkPosX1 = -1; // we will populate these values while drawing the cells
		int insertMarkPosX2 = -1;
		int insertMarkPosY = -1;
		boolean insertMarkPosFound = false;

		GridCellSpanManager cellSpanManager = new GridCellSpanManager();

		e.gc.setBackground(getBackground());
		this.drawBackground(e.gc, 0, 0, getSize().x, getSize().y);

		if (scrollValuesObsolete) {
			updateScrollbars();
			scrollValuesObsolete = false;
		}

		int x = 0;
		int y = 0;

		if (columnHeadersVisible) {
			paintHeader(e.gc);
			y += headerHeight;
		}

		int availableHeight = getClientArea().height - y;
		int visibleRows = availableHeight / getItemHeight() + 1;
		if (items.size() > 0 && availableHeight > 0) {
			RowRange range = getRowRange(getTopIndex(), availableHeight, false, false);
			if (range.height >= availableHeight) visibleRows = range.rows;
			else visibleRows = range.rows + (availableHeight - range.height) / getItemHeight() + 1;
		}

		int firstVisibleIndex = getTopIndex();
		int firstItemToDraw = firstVisibleIndex;

		if (hasSpanning) {
			// We need to find the first Item to draw. An earlier item can row-span the first visible item.
			for (int rowIndex = 0; rowIndex < firstVisibleIndex; rowIndex++) {
				GridItem itemForRow = (GridItem) items.get(rowIndex);
				int colIndex = 0;

				int maxRowSpanForItem = 0;
				for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
					GridColumn column = (GridColumn) columnIterator.next();

					if (!column.isVisible()) {
						colIndex++;
						continue;
					}

					int rowSpan = itemForRow.getRowSpan(colIndex);
					maxRowSpanForItem = rowSpan > maxRowSpanForItem ? rowSpan : maxRowSpanForItem;
					colIndex++;
				}

				if (rowIndex + maxRowSpanForItem >= firstVisibleIndex) {
					firstItemToDraw = rowIndex;
					break;
				} else {
					rowIndex += maxRowSpanForItem;
				}
			}

			for (int rowIndex = firstItemToDraw; rowIndex < firstVisibleIndex; rowIndex++) {
				GridItem itemForRow = (GridItem) items.get(rowIndex);
				y = y - itemForRow.getHeight() - 1;
			}
		}

		int row = firstItemToDraw;

		for (int i = 0; i < visibleRows + (firstVisibleIndex - firstItemToDraw); i++) {

			x = 0;

			x -= getHScrollSelectionInPixels();

			// get the item to draw
			GridItem item = null;
			if (row < items.size()) {
				item = (GridItem) items.get(row);

				while (!item.isVisible() && row < items.size() - 1) {
					row++;
					item = (GridItem) items.get(row);
				}
			}
			if (item != null && !item.isVisible()) {
				item = null;
			}

			if (item != null) {
				boolean cellInRowSelected = false;

				if (rowHeaderVisible) {

					// row header is actually painted later
					x += rowHeaderWidth;
				}

				int focusY = y;

				int colIndex = 0;

				// draw regular cells for each column
				for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {

					GridColumn column = (GridColumn) columnIterator.next();
					boolean skipCell = cellSpanManager.skipCell(colIndex, row);
					int indexOfColumn = indexOf(column);

					if (!column.isVisible()) {
						colIndex++;
						if (skipCell) {
							cellSpanManager.consumeCell(colIndex, row);
						}
						continue;
					}

					int width = item.getCellSize(indexOfColumn).x;

					if (skipCell == false) {

						int nrRowsToSpan = item.getRowSpan(indexOfColumn);
						int nrColumnsToSpan = item.getColumnSpan(indexOfColumn);

						if (nrRowsToSpan > 0 || nrColumnsToSpan > 0) {
							cellSpanManager.addCellSpanInfo(colIndex, row, nrColumnsToSpan, nrRowsToSpan);
						}

						if (x + width >= 0 && x < getClientArea().width) {
							Point sizeOfColumn = item.getCellSize(indexOfColumn);

							column.getCellRenderer().setBounds(x, y, width, sizeOfColumn.y);
							int cellInHeaderDelta = columnHeadersVisible ? headerHeight - y : 0;
							if (cellInHeaderDelta > 0) {
								e.gc.setClipping(new Rectangle(x - 1, y + cellInHeaderDelta, width + 1, sizeOfColumn.y + 2 - cellInHeaderDelta));
							} else {
								e.gc.setClipping(new Rectangle(x - 1, y - 1, width + 1, sizeOfColumn.y + 2));
							}

							column.getCellRenderer().setRow(i + 1);

							column.getCellRenderer().setSelected(selectedItems.contains(item));
							column.getCellRenderer().setFocus(this.isFocusControl());
							column.getCellRenderer().setRowFocus(focusItem == item);
							column.getCellRenderer().setCellFocus(cellSelectionEnabled && focusItem == item && focusColumn == column);

							column.getCellRenderer().setRowHover(hoveringItem == item);
							column.getCellRenderer().setColumnHover(hoveringColumn == column);

							column.getCellRenderer().setColumn(indexOfColumn);

							if (selectedCells.contains(new Point(indexOfColumn, row))) {
								column.getCellRenderer().setCellSelected(true);
								cellInRowSelected = true;
							} else {
								column.getCellRenderer().setCellSelected(false);
							}

							if (hoveringItem == item && hoveringColumn == column) {
								column.getCellRenderer().setHoverDetail(hoveringDetail);
							} else {
								column.getCellRenderer().setHoverDetail("");
							}

							column.getCellRenderer().paint(e.gc, item);

							e.gc.setClipping((Rectangle) null);

							// collect the insertMark position
							if (!insertMarkPosFound && insertMarkItem == item && (insertMarkColumn == null || insertMarkColumn == column)) {
								// y-pos
								insertMarkPosY = y - 1;
								if (!insertMarkBefore) insertMarkPosY += item.getHeight() + 1;
								// x1-pos
								insertMarkPosX1 = x;
								if (column.isTree()) {
									insertMarkPosX1 += Math.min(width, column.getCellRenderer().getTextBounds(item, false).x);
								}

								// x2-pos
								if (insertMarkColumn == null) {
									insertMarkPosX2 = getClientArea().x + getClientArea().width;
								} else {
									insertMarkPosX2 = x + width;
								}

								insertMarkPosFound = true;
							}
						}
					} else {
						cellSpanManager.consumeCell(colIndex, row);
					}

					x += column.getWidth();
					colIndex++;

				}

				if (x < getClientArea().width) {
					// insertMarkPos needs correction
					if (insertMarkPosFound && insertMarkColumn == null) insertMarkPosX2 = x;

					emptyCellRenderer.setSelected(selectedItems.contains(item));
					emptyCellRenderer.setFocus(this.isFocusControl());
					emptyCellRenderer.setRow(i + 1);
					emptyCellRenderer.setBounds(x, y, getClientArea().width - x + 1, item.getHeight());
					emptyCellRenderer.setColumn(getColumnCount());
					emptyCellRenderer.paint(e.gc, item);
				}

				x = 0;

				if (rowHeaderVisible) {

					if (!cellSelectionEnabled) {
						rowHeaderRenderer.setSelected(selectedItems.contains(item));
					} else {
						rowHeaderRenderer.setSelected(cellInRowSelected);
					}
					if (y >= headerHeight) {
						rowHeaderRenderer.setBounds(0, y, rowHeaderWidth, item.getHeight() + 1);
						rowHeaderRenderer.paint(e.gc, item);
					}
					x += rowHeaderWidth;
				}

				// focus
				if (isFocusControl() && !cellSelectionEnabled) {
					if (item == focusItem) {
						if (focusRenderer != null) {
							int focusX = 0;
							if (rowHeaderVisible) {
								focusX = rowHeaderWidth;
							}
							focusRenderer.setBounds(focusX, focusY - 1, getClientArea().width - focusX - 1, item.getHeight() + 1);
							focusRenderer.paint(e.gc, item);
						}
					}
				}

				y += item.getHeight() + 1;
			} else {

				if (rowHeaderVisible) {
					// row header is actually painted later
					x += rowHeaderWidth;
				}

				emptyCellRenderer.setBounds(x, y, getClientArea().width - x, getItemHeight());
				emptyCellRenderer.setFocus(false);
				emptyCellRenderer.setSelected(false);
				emptyCellRenderer.setRow(i + 1);

				for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
					GridColumn column = (GridColumn) columnIterator.next();

					if (column.isVisible()) {
						emptyCellRenderer.setBounds(x, y, column.getWidth(), getItemHeight());
						emptyCellRenderer.setColumn(indexOf(column));
						emptyCellRenderer.paint(e.gc, this);

						x += column.getWidth();
					}
				}

				if (x < getClientArea().width) {
					emptyCellRenderer.setBounds(x, y, getClientArea().width - x + 1, getItemHeight());
					emptyCellRenderer.setColumn(getColumnCount());
					emptyCellRenderer.paint(e.gc, this);
				}

				x = 0;

				if (rowHeaderVisible) {
					emptyRowHeaderRenderer.setBounds(x, y, rowHeaderWidth, getItemHeight() + 1);
					emptyRowHeaderRenderer.paint(e.gc, this);

					x += rowHeaderWidth;
				}

				y += getItemHeight() + 1;
			}

			row++;
		}

		// draw drop point
		if (draggingColumn) {
			if ((dragDropAfterColumn != null || dragDropBeforeColumn != null) && (dragDropAfterColumn != columnBeingPushed && dragDropBeforeColumn != columnBeingPushed) && dragDropPointValid) {
				if (dragDropBeforeColumn != null) {
					x = getColumnHeaderXPosition(dragDropBeforeColumn);
				} else {
					x = getColumnHeaderXPosition(dragDropAfterColumn) + dragDropAfterColumn.getWidth();
				}

				Point size = dropPointRenderer.computeSize(e.gc, SWT.DEFAULT, SWT.DEFAULT, null);
				x -= size.x / 2;
				if (x < 0) {
					x = 0;
				}
				dropPointRenderer.setBounds(x - 1, headerHeight + DROP_POINT_LOWER_OFFSET, size.x, size.y);
				dropPointRenderer.paint(e.gc, null);
			}
		}

		// draw insertion mark
		if (insertMarkPosFound) {
			e.gc.setClipping(rowHeaderVisible ? rowHeaderWidth : 0, columnHeadersVisible ? headerHeight : 0, getClientArea().width, getClientArea().height);
			insertMarkRenderer.paint(e.gc, new Rectangle(insertMarkPosX1, insertMarkPosY, insertMarkPosX2 - insertMarkPosX1, 0));
		}

		if (columnFootersVisible) {
			paintFooter(e.gc);
		}
	}

	/**
	 * Returns a column reference if the x,y coordinates are over a column header (header only).
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return column reference which mouse is over, or null.
	 */
	private GridColumn overColumnHeader(int x, int y) {
		GridColumn col = null;

		if (y <= headerHeight && y > 0) {
			col = getColumn(new Point(x, y));
			if (col != null && col.getColumnGroup() != null) {
				if (y <= groupHeaderHeight) {
					return null;
				}
			}
		}

		return col;
	}

	/**
	 * Returns a column reference if the x,y coordinates are over a column header (header only).
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return column reference which mouse is over, or null.
	 */
	private GridColumn overColumnFooter(int x, int y) {
		GridColumn col = null;

		if (y >= getClientArea().height - footerHeight) {
			col = getColumn(new Point(x, y));
		}

		return col;
	}

	/**
	 * Returns a column group reference if the x,y coordinates are over a column group header (header only).
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return column group reference which mouse is over, or null.
	 */
	private GridColumnGroup overColumnGroupHeader(int x, int y) {
		GridColumnGroup group = null;

		if (y <= groupHeaderHeight && y > 0) {
			GridColumn col = getColumn(new Point(x, y));
			if (col != null) {
				group = col.getColumnGroup();
			}
		}

		return group;
	}

	/**
	 * Paints the header.
	 * 
	 * @param gc gc from paint event
	 */
	private void paintHeader(GC gc) {
		int x = 0;
		int y = 0;

		x -= getHScrollSelectionInPixels();

		if (rowHeaderVisible) {
			// paint left corner
			// topLeftRenderer.setBounds(0, y, rowHeaderWidth, headerHeight);
			// topLeftRenderer.paint(gc, null);
			x += rowHeaderWidth;
		}

		GridColumnGroup previousPaintedGroup = null;

		for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
			if (x > getClientArea().width) break;

			GridColumn column = (GridColumn) columnIterator.next();
			int height = 0;

			if (!column.isVisible()) {
				continue;
			}

			if (column.getColumnGroup() != null) {

				if (column.getColumnGroup() != previousPaintedGroup) {
					int width = column.getWidth();

					GridColumn nextCol = null;
					if (displayOrderedColumns.indexOf(column) + 1 < displayOrderedColumns.size()) {
						nextCol = (GridColumn) displayOrderedColumns.get(displayOrderedColumns.indexOf(column) + 1);
					}

					while (nextCol != null && nextCol.getColumnGroup() == column.getColumnGroup()) {

						if ((nextCol.getColumnGroup().getExpanded() && !nextCol.isDetail()) || (!nextCol.getColumnGroup().getExpanded() && !nextCol.isSummary())) {} else if (nextCol.isVisible()) {
							width += nextCol.getWidth();
						}

						if (displayOrderedColumns.indexOf(nextCol) + 1 < displayOrderedColumns.size()) {
							nextCol = (GridColumn) displayOrderedColumns.get(displayOrderedColumns.indexOf(nextCol) + 1);
						} else {
							nextCol = null;
						}
					}

					boolean selected = true;

					for (int i = 0; i < column.getColumnGroup().getColumns().length; i++) {
						GridColumn col = column.getColumnGroup().getColumns()[i];
						if (col.isVisible() && (column.getMoveable() || !selectedColumns.contains(col))) {
							selected = false;
							break;
						}
					}

					column.getColumnGroup().getHeaderRenderer().setSelected(selected);
					column.getColumnGroup().getHeaderRenderer().setHover(hoverColumnGroupHeader == column.getColumnGroup());
					column.getColumnGroup().getHeaderRenderer().setHoverDetail(hoveringDetail);

					column.getColumnGroup().getHeaderRenderer().setBounds(x, 0, width, groupHeaderHeight);

					column.getColumnGroup().getHeaderRenderer().paint(gc, column.getColumnGroup());

					previousPaintedGroup = column.getColumnGroup();
				}

				height = headerHeight - groupHeaderHeight;
				y = groupHeaderHeight;
			} else {
				height = headerHeight;
				y = 0;
			}

			if (pushingColumn) {
				column.getHeaderRenderer().setHover(columnBeingPushed == column && pushingAndHovering);
			} else {
				column.getHeaderRenderer().setHover(hoveringColumnHeader == column);
			}

			column.getHeaderRenderer().setHoverDetail(hoveringDetail);

			column.getHeaderRenderer().setBounds(x, y, column.getWidth(), height);

			if (cellSelectionEnabled) column.getHeaderRenderer().setSelected(selectedColumns.contains(column));

			if (x + column.getWidth() >= 0) {
				column.getHeaderRenderer().paint(gc, column);
			}

			x += column.getWidth();
		}

		if (x < getClientArea().width) {
			emptyColumnHeaderRenderer.setBounds(x, 0, getClientArea().width - x, headerHeight);
			emptyColumnHeaderRenderer.paint(gc, null);
		}

		x = 0;

		if (rowHeaderVisible) {
			// paint left corner
			topLeftRenderer.setBounds(0, 0, rowHeaderWidth, headerHeight);
			topLeftRenderer.paint(gc, this);
			x += rowHeaderWidth;
		}

		if (draggingColumn) {

			gc.setAlpha(COLUMN_DRAG_ALPHA);

			columnBeingPushed.getHeaderRenderer().setSelected(false);

			int height = 0;

			if (columnBeingPushed.getColumnGroup() != null) {
				height = headerHeight - groupHeaderHeight;
				y = groupHeaderHeight;
			} else {
				height = headerHeight;
				y = 0;
			}

			columnBeingPushed.getHeaderRenderer().setBounds(getColumnHeaderXPosition(columnBeingPushed) + (currentHeaderDragX - startHeaderDragX), y, columnBeingPushed.getWidth(), height);
			columnBeingPushed.getHeaderRenderer().paint(gc, columnBeingPushed);
			columnBeingPushed.getHeaderRenderer().setSelected(false);

			gc.setAlpha(-1);
			gc.setAdvanced(false);
		}

	}

	private void paintFooter(GC gc) {
		int x = 0;
		int y = 0;

		x -= getHScrollSelectionInPixels();

		if (rowHeaderVisible) {
			// paint left corner
			// topLeftRenderer.setBounds(0, y, rowHeaderWidth, headerHeight);
			// topLeftRenderer.paint(gc, null);
			x += rowHeaderWidth;
		}

		for (Iterator columnIterator = displayOrderedColumns.iterator(); columnIterator.hasNext();) {
			if (x > getClientArea().width) break;

			GridColumn column = (GridColumn) columnIterator.next();
			int height = 0;

			if (!column.isVisible()) {
				continue;
			}

			height = footerHeight;
			y = getClientArea().height - height;

			column.getFooterRenderer().setBounds(x, y, column.getWidth(), height);
			if (x + column.getWidth() >= 0) {
				column.getFooterRenderer().paint(gc, column);
			}

			x += column.getWidth();
		}

		if (x < getClientArea().width) {
			emptyColumnFooterRenderer.setBounds(x, getClientArea().height - footerHeight, getClientArea().width - x, footerHeight);
			emptyColumnFooterRenderer.paint(gc, null);
		}

		if (rowHeaderVisible) {
			// paint left corner
			bottomLeftRenderer.setBounds(0, getClientArea().height - footerHeight, rowHeaderWidth, footerHeight);
			bottomLeftRenderer.paint(gc, this);
			x += rowHeaderWidth;
		}
	}

	/**
	 * Manages the state of the scrollbars when new items are added or the bounds are changed.
	 */
	private void updateScrollbars() {
		Point preferredSize = getTableSize();

		Rectangle clientArea = getClientArea();

		// First, figure out if the scrollbars should be visible and turn them
		// on right away
		// this will allow the computations further down to accommodate the
		// correct client
		// area

		// Turn the scrollbars on if necessary and do it all over again if
		// necessary. This ensures
		// that if a scrollbar is turned on/off, the other scrollbar's
		// visibility may be affected (more
		// area may have been added/removed.
		for (int doublePass = 1; doublePass <= 2; doublePass++) {

			if (preferredSize.y > clientArea.height) {
				vScroll.setVisible(true);
			} else {
				vScroll.setVisible(false);
				vScroll.setValues(0, 0, 1, 1, 1, 1);
			}
			if (preferredSize.x > clientArea.width) {
				hScroll.setVisible(true);
			} else {
				hScroll.setVisible(false);
				hScroll.setValues(0, 0, 1, 1, 1, 1);
			}

			// get the clientArea again with the now visible/invisible
			// scrollbars
			clientArea = getClientArea();
		}

		// if the scrollbar is visible set its values
		if (vScroll.getVisible()) {
			int max = currentVisibleItems;
			int thumb = 1;

			if (!hasDifferingHeights) {
				// in this case, the number of visible rows on screen is constant,
				// so use this as thumb
				thumb = (getVisibleGridHeight() + 1) / (getItemHeight() + 1);
			} else {
				// in this case, the number of visible rows on screen is variable,
				// so we have to use 1 as thumb and decrease max by the number of
				// rows on the last page
				if (getVisibleGridHeight() >= 1) {
					RowRange range = getRowRange(-1, getVisibleGridHeight(), true, true);
					max -= range.rows - 1;
				}
			}

			// if possible, remember selection, if selection is too large, just
			// make it the max you can
			int selection = Math.min(vScroll.getSelection(), max);

			vScroll.setValues(selection, 0, max, thumb, 1, thumb);
		}

		// if the scrollbar is visible set its values
		if (hScroll.getVisible()) {

			if (!columnScrolling) {
				// horizontal scrolling works pixel by pixel

				int hiddenArea = preferredSize.x - clientArea.width + 1;

				// if possible, remember selection, if selection is too large,
				// just
				// make it the max you can
				int selection = Math.min(hScroll.getSelection(), hiddenArea - 1);

				hScroll.setValues(selection, 0, hiddenArea + clientArea.width - 1, clientArea.width, HORZ_SCROLL_INCREMENT, clientArea.width);
			} else {
				// horizontal scrolling is column by column

				int hiddenArea = preferredSize.x - clientArea.width + 1;

				int max = 0;
				int i = 0;

				while (hiddenArea > 0 && i < getColumnCount()) {
					GridColumn col = (GridColumn) displayOrderedColumns.get(i);

					i++;

					if (col.isVisible()) {
						hiddenArea -= col.getWidth();
						max++;
					}
				}

				max++;

				// max should never be greater than the number of visible cols
				int visCols = 0;
				for (Iterator iter = columns.iterator(); iter.hasNext();) {
					GridColumn element = (GridColumn) iter.next();
					if (element.isVisible()) {
						visCols++;
					}
				}
				max = Math.min(visCols, max);

				// if possible, remember selection, if selection is too large,
				// just
				// make it the max you can
				int selection = Math.min(hScroll.getSelection(), max);

				hScroll.setValues(selection, 0, max, 1, 1, 1);
			}
		}

	}

	/**
	 * Adds/removes items from the selected items list based on the selection/deselection of the given item.
	 * 
	 * @param item item being selected/unselected
	 * @param stateMask key state during selection
	 * 
	 * @return selection event that needs to be fired or null
	 */
	private Event updateSelection(GridItem item, int stateMask) {
		if (!selectionEnabled) {
			return null;
		}

		Event selectionEvent = null;

		if (selectionType == SWT.SINGLE) {
			if (selectedItems.contains(item)) {
				// Deselect when pressing CTRL
				if ((stateMask & SWT.MOD1) == SWT.MOD1) {
					selectedItems.clear();
				}
			} else {
				selectedItems.clear();
				selectedItems.add(item);
			}
			Rectangle clientArea = getClientArea();
			redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

			selectionEvent = new Event();
			selectionEvent.item = item;
		} else if (selectionType == SWT.MULTI) {
			boolean shift = false;
			boolean ctrl = false;

			if ((stateMask & SWT.MOD2) == SWT.MOD2) {
				shift = true;
			}

			if ((stateMask & SWT.MOD1) == SWT.MOD1) {
				ctrl = true;
			}

			if (!shift && !ctrl) {
				if (selectedItems.size() == 1 && selectedItems.contains(item)) return null;

				selectedItems.clear();

				selectedItems.add(item);

				Rectangle clientArea = getClientArea();
				redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

				shiftSelectionAnchorItem = null;

				selectionEvent = new Event();
				selectionEvent.item = item;
			} else if (shift) {

				if (shiftSelectionAnchorItem == null) {
					shiftSelectionAnchorItem = focusItem;
				}

				// if (shiftSelectionAnchorItem == item)
				// {
				// return;
				// }

				boolean maintainAnchorSelection = false;

				if (!ctrl) {
					if (selectedItems.contains(shiftSelectionAnchorItem)) {
						maintainAnchorSelection = true;
					}
					selectedItems.clear();
				}

				int anchorIndex = items.indexOf(shiftSelectionAnchorItem);
				int itemIndex = items.indexOf(item);

				int min = 0;
				int max = 0;

				if (anchorIndex < itemIndex) {
					if (maintainAnchorSelection) {
						min = anchorIndex;
					} else {
						min = anchorIndex + 1;
					}
					max = itemIndex;
				} else {
					if (maintainAnchorSelection) {
						max = anchorIndex;
					} else {
						max = anchorIndex - 1;
					}
					min = itemIndex;
				}

				for (int i = min; i <= max; i++) {
					if (!selectedItems.contains(items.get(i)) && ((GridItem) items.get(i)).isVisible()) {
						selectedItems.add((GridItem) items.get(i));
					}
				}
				Rectangle clientArea = getClientArea();
				redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

				selectionEvent = new Event();
			} else if (ctrl) {
				if (selectedItems.contains(item)) {
					selectedItems.remove(item);
				} else {
					selectedItems.add(item);
				}
				Rectangle clientArea = getClientArea();
				redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

				shiftSelectionAnchorItem = null;

				selectionEvent = new Event();
				selectionEvent.item = item;
			}
		}

		Rectangle clientArea = getClientArea();
		redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

		return selectionEvent;
	}

	/**
	 * Updates cell selection.
	 * 
	 * @param newCell newly clicked, navigated to cell.
	 * @param stateMask statemask during preceeding mouse or key event.
	 * @param dragging true if the user is dragging.
	 * @param reverseDuplicateSelections true if the user is reversing selection rather than adding to.
	 * 
	 * @return selection event that will need to be fired or null.
	 */
	private Event updateCellSelection(Point newCell, int stateMask, boolean dragging, boolean reverseDuplicateSelections) {
		Vector v = new Vector();
		v.add(newCell);
		return updateCellSelection(v, stateMask, dragging, reverseDuplicateSelections);
	}

	/**
	 * Updates cell selection.
	 * 
	 * @param newCell newly clicked, navigated to cells.
	 * @param stateMask statemask during preceeding mouse or key event.
	 * @param dragging true if the user is dragging.
	 * @param reverseDuplicateSelections true if the user is reversing selection rather than adding to.
	 * 
	 * @return selection event that will need to be fired or null.
	 */
	private Event updateCellSelection(Vector newCells, int stateMask, boolean dragging, boolean reverseDuplicateSelections) {
		boolean shift = false;
		boolean ctrl = false;

		if ((stateMask & SWT.MOD2) == SWT.MOD2) {
			shift = true;
		} else {
			shiftSelectionAnchorColumn = null;
			shiftSelectionAnchorItem = null;
		}

		if ((stateMask & SWT.MOD1) == SWT.MOD1) {
			ctrl = true;
		}

		if (!shift && !ctrl) {
			if (newCells.equals(selectedCells)) return null;

			selectedCells.clear();
			for (int i = 0; i < newCells.size(); i++) {
				addToCellSelection((Point) newCells.get(i));
			}

		} else if (shift) {

			Point newCell = (Point) newCells.get(0); // shift selection should only occur with one
			// cell, ignoring others

			if ((focusColumn == null) || (focusItem == null)) {
				return null;
			}

			shiftSelectionAnchorColumn = getColumn(newCell.x);
			shiftSelectionAnchorItem = getItem(newCell.y);

			if (ctrl) {
				selectedCells.clear();
				selectedCells.addAll(selectedCellsBeforeRangeSelect);
			} else {
				selectedCells.clear();
			}

			GridColumn currentColumn = focusColumn;
			GridItem currentItem = focusItem;

			GridColumn endColumn = getColumn(newCell.x);
			GridItem endItem = getItem(newCell.y);

			Point newRange = getSelectionRange(currentItem, currentColumn, endItem, endColumn);

			currentColumn = getColumn(newRange.x);
			endColumn = getColumn(newRange.y);

			GridColumn startCol = currentColumn;

			if (indexOf(currentItem) > indexOf(endItem)) {
				GridItem temp = currentItem;
				currentItem = endItem;
				endItem = temp;
			}

			boolean firstLoop = true;

			do {
				if (!firstLoop) {
					currentItem = getNextVisibleItem(currentItem);
				}

				firstLoop = false;

				boolean firstLoop2 = true;

				currentColumn = startCol;

				do {
					if (!firstLoop2) {
						int index = displayOrderedColumns.indexOf(currentColumn) + 1;

						if (index < displayOrderedColumns.size()) {
							currentColumn = getVisibleColumn_DegradeRight(currentItem, (GridColumn) displayOrderedColumns.get(index));
						} else {
							currentColumn = null;
						}

						if (currentColumn != null) if (displayOrderedColumns.indexOf(currentColumn) > displayOrderedColumns.indexOf(endColumn)) currentColumn = null;
					}

					firstLoop2 = false;

					if (currentColumn != null) {
						Point cell = new Point(indexOf(currentColumn), indexOf(currentItem));
						addToCellSelection(cell);
					}
				} while (currentColumn != endColumn && currentColumn != null);
			} while (currentItem != endItem);
		} else if (ctrl) {
			boolean reverse = reverseDuplicateSelections;
			if (!selectedCells.containsAll(newCells)) reverse = false;

			if (dragging) {
				selectedCells.clear();
				selectedCells.addAll(selectedCellsBeforeRangeSelect);
			}

			if (reverse) {
				selectedCells.removeAll(newCells);
			} else {
				for (int i = 0; i < newCells.size(); i++) {
					addToCellSelection((Point) newCells.get(i));
				}
			}
		}

		updateColumnSelection();

		Event e = new Event();
		if (dragging) {
			e.detail = SWT.DRAG;
			followupCellSelectionEventOwed = true;
		}

		Rectangle clientArea = getClientArea();
		redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

		return e;
	}

	private void addToCellSelection(Point newCell) {
		if (newCell.x < 0 || newCell.x >= columns.size()) return;

		if (newCell.y < 0 || newCell.y >= items.size()) return;

		if (getColumn(newCell.x).getCellSelectionEnabled()) {
			Iterator it = selectedCells.iterator();
			boolean found = false;
			while (it.hasNext()) {
				Point p = (Point) it.next();
				if (newCell.equals(p)) {
					found = true;
					break;
				}
			}

			if (!found) {
				selectedCells.add(newCell);
			}
		}
	}

	void updateColumnSelection() {
		// Update the list of which columns have all their cells selected
		selectedColumns.clear();

		for (Iterator iter = selectedCells.iterator(); iter.hasNext();) {
			Point cell = (Point) iter.next();

			GridColumn col = getColumn(cell.x);

			selectedColumns.add(col);
		}
	}

	/**
	 * Initialize all listeners.
	 */
	private void initListeners() {
		disposeListener = new Listener() {
			public void handleEvent(Event e) {
				onDispose(e);
			}
		};
		addListener(SWT.Dispose, disposeListener);

		addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				onPaint(e);
			}
		});

		addListener(SWT.Resize, new Listener() {
			public void handleEvent(Event e) {
				onResize();
			}
		});

		if (getVerticalBar() != null) {
			getVerticalBar().addListener(SWT.Selection, new Listener() {
				public void handleEvent(Event e) {
					onScrollSelection();
				}
			});
		}

		if (getHorizontalBar() != null) {
			getHorizontalBar().addListener(SWT.Selection, new Listener() {
				public void handleEvent(Event e) {
					onScrollSelection();
				}
			});
		}

		addListener(SWT.KeyDown, new Listener() {
			public void handleEvent(Event e) {
				onKeyDown(e);
			}
		});

		addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent e) {
				e.doit = true;
			}
		});

		addMouseListener(new MouseListener() {
			public void mouseDoubleClick(MouseEvent e) {
				onMouseDoubleClick(e);
			}

			public void mouseDown(MouseEvent e) {
				onMouseDown(e);
			}

			public void mouseUp(MouseEvent e) {
				onMouseUp(e);
			}
		});

		addMouseMoveListener(new MouseMoveListener() {
			public void mouseMove(MouseEvent e) {
				onMouseMove(e);
			}
		});

		addMouseTrackListener(new MouseTrackListener() {
			public void mouseEnter(MouseEvent e) {}

			public void mouseExit(MouseEvent e) {
				onMouseExit(e);
			}

			public void mouseHover(MouseEvent e) {}
		});

		addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {
				onFocusIn();
				redraw();
			}

			public void focusLost(FocusEvent e) {
				redraw();
			}
		});

		// Special code to reflect mouse wheel events if using an external
		// scroller
		addListener(SWT.MouseWheel, new Listener() {
			public void handleEvent(Event e) {
				onMouseWheel(e);
			}
		});
	}

	private void onFocusIn() {
		if (!items.isEmpty() && focusItem == null) {
			focusItem = (GridItem) items.get(0);
		}
	}

	private void onDispose(Event event) {
		// We only want to dispose of our items and such *after* anybody else who may have been
		// listening to the dispose has had a chance to do whatever.
		removeListener(SWT.Dispose, disposeListener);
		notifyListeners(SWT.Dispose, event);
		event.type = SWT.None;

		disposing = true;

		cellHeaderSelectionBackground.dispose();

		for (Iterator iterator = items.iterator(); iterator.hasNext();) {
			GridItem item = (GridItem) iterator.next();
			item.dispose();
		}

		for (int i = 0; i < columnGroups.length; i++) {
			columnGroups[i].dispose();
		}

		for (Iterator iterator = columns.iterator(); iterator.hasNext();) {
			GridColumn col = (GridColumn) iterator.next();
			col.dispose();
		}

		sizingGC.dispose();
	}

	/**
	 * Mouse wheel event handler.
	 * 
	 * @param e event
	 */
	private void onMouseWheel(Event e) {
		if (vScroll.getVisible()) {
			vScroll.handleMouseWheel(e);
			if (getVerticalBar() == null) e.doit = false;
		} else if (hScroll.getVisible()) {
			hScroll.handleMouseWheel(e);
			if (getHorizontalBar() == null) e.doit = false;
		}
	}

	/**
	 * Mouse down event handler.
	 * 
	 * @param e event
	 */
	private void onMouseDown(MouseEvent e) {
		// for some reason, SWT prefers the children to get focus if
		// there are any children
		// the setFocus method on Composite will not set focus to the
		// Composite if one of its
		// children can get focus instead. This only affects the table
		// when an editor is open
		// and therefore the table has a child. The solution is to
		// forceFocus()
		if ((getStyle() & SWT.NO_FOCUS) != SWT.NO_FOCUS) {
			forceFocus();
		}

		hideToolTip();

		// if populated will be fired at end of method.
		Event selectionEvent = null;

		cellSelectedOnLastMouseDown = false;
		cellRowSelectedOnLastMouseDown = false;
		cellColumnSelectedOnLastMouseDown = false;

		if (hoveringOnColumnResizer) {
			if (e.button == 1) {
				resizingColumn = true;
				resizingStartX = e.x;
				resizingColumnStartWidth = columnBeingResized.getWidth();
			}
			return;
		}
		if (rowsResizeable && hoveringOnRowResizer) {
			if (e.button == 1) {
				resizingRow = true;
				resizingStartY = e.y;
				resizingRowStartHeight = rowBeingResized.getHeight();
			}
			return;
		}

		if (e.button == 1 && handleColumnHeaderPush(e.x, e.y)) {
			return;
		}

		if (e.button == 1 && handleColumnGroupHeaderClick(e.x, e.y)) {
			return;
		}

		if (e.button == 1 && handleColumnFooterPush(e.x, e.y)) {
			return;
		}

		GridItem item = getItem(new Point(e.x, e.y));

		if (e.button == 1 && item != null && handleCellClick(item, e.x, e.y)) {
			return;
		}

		if (isListening(SWT.DragDetect)) {
			if ((cellSelectionEnabled && hoveringOnSelectionDragArea) || (!cellSelectionEnabled && item != null && selectedItems.contains(item))) {
				if (dragDetect(e)) {
					return;
				}
			}
		}

		if (item != null) {
			if (cellSelectionEnabled) {
				GridColumn col = getColumn(new Point(e.x, e.y));
				boolean isSelectedCell = false;
				if (col != null) isSelectedCell = selectedCells.contains(new Point(indexOf(col), indexOf(item)));

				if (e.button == 1 || (e.button == 3 && col != null && !isSelectedCell)) {
					if (col != null) {
						selectionEvent = updateCellSelection(new Point(indexOf(col), indexOf(item)), e.stateMask, false, true);
						cellSelectedOnLastMouseDown = (getCellSelectionCount() > 0);

						if (e.stateMask != SWT.MOD2) {
							focusColumn = col;
							focusItem = item;
						}
						// showColumn(col);
						showItem(item);
						redraw();
					} else if (rowHeaderVisible) {
						if (e.x <= rowHeaderWidth) {

							boolean shift = ((e.stateMask & SWT.MOD2) != 0);
							boolean ctrl = false;
							if (!shift) {
								ctrl = ((e.stateMask & SWT.MOD1) != 0);
							}

							Vector cells = new Vector();

							if (shift) {
								getCells(item, focusItem, cells);
							} else {
								getCells(item, cells);
							}

							int newStateMask = SWT.NONE;
							if (ctrl) newStateMask = SWT.MOD1;

							selectionEvent = updateCellSelection(cells, newStateMask, shift, ctrl);
							cellRowSelectedOnLastMouseDown = (getCellSelectionCount() > 0);

							if (!shift) {
								// set focus back to the first visible column
								focusColumn = getColumn(new Point(rowHeaderWidth + 1, e.y));

								focusItem = item;
							}
							showItem(item);
							redraw();
						}
					}
					intendedFocusColumn = focusColumn;
				}
			} else {
				if (e.button == 2 || e.button > 3) {
					return;
				}

				if (e.button == 3 && selectionType == SWT.MULTI) {
					if ((e.stateMask & SWT.MOD2) == SWT.MOD2) {
						return;
					}

					if ((e.stateMask & SWT.MOD1) == SWT.MOD1) {
						return;
					}

					if (selectedItems.contains(item)) {
						return;
					}
				}
				selectionEvent = updateSelection(item, e.stateMask);

				focusItem = item;
				showItem(item);
				redraw();
			}
		} else if (e.button == 1 && rowHeaderVisible && e.x <= rowHeaderWidth && e.y < headerHeight) {
			// Nothing to select
			if (items.size() == 0) {
				return;
			}
			if (cellSelectionEnabled) {
				// click on the top left corner means select everything
				selectionEvent = selectAllCellsInternal();

				focusColumn = getColumn(new Point(rowHeaderWidth + 1, 1));
			} else {
				// click on the top left corner means select everything
				selectionEvent = selectAllRowsInternal();
			}
			focusItem = getItem(getTopIndex());
		} else if (cellSelectionEnabled && e.button == 1 && columnHeadersVisible && e.y <= headerHeight) {
			// column cell selection
			GridColumn col = getColumn(new Point(e.x, e.y));

			if (col == null) return;

			if (getItemCount() == 0) return;

			Vector cells = new Vector();

			GridColumnGroup group = col.getColumnGroup();
			if (group != null && e.y < groupHeaderHeight) {
				getCells(group, cells);
			} else {
				getCells(col, cells);
			}

			selectionEvent = updateCellSelection(cells, e.stateMask, false, true);
			cellColumnSelectedOnLastMouseDown = (getCellSelectionCount() > 0);

			GridItem newFocusItem = getItem(0);

			while (newFocusItem != null && getSpanningColumn(newFocusItem, col) != null) {
				newFocusItem = getNextVisibleItem(newFocusItem);
			}

			if (newFocusItem != null) {
				focusColumn = col;
				focusItem = newFocusItem;
			}

			showColumn(col);
			redraw();
		}

		if (selectionEvent != null) {
			selectionEvent.stateMask = e.stateMask;
			selectionEvent.button = e.button;
			selectionEvent.item = item;
			selectionEvent.x = e.x;
			selectionEvent.y = e.y;
			notifyListeners(SWT.Selection, selectionEvent);

			if (!cellSelectionEnabled) {
				if (isListening(SWT.DragDetect)) {
					dragDetect(e);
				}
			}
		}

	}

	/**
	 * Mouse double click event handler.
	 * 
	 * @param e event
	 */
	private void onMouseDoubleClick(MouseEvent e) {
		if (e.button == 1) {

			if (hoveringOnColumnResizer) {
				columnBeingResized.pack();
				columnBeingResized.fireResized();
				for (int index = displayOrderedColumns.indexOf(columnBeingResized) + 1; index < displayOrderedColumns.size(); index++) {
					GridColumn col = (GridColumn) displayOrderedColumns.get(index);
					if (col.isVisible()) col.fireMoved();
				}
				resizingColumn = false;
				handleHoverOnColumnResizer(e.x, e.y);
				return;
			} else if (rowsResizeable && hoveringOnRowResizer) {
				List sel = Arrays.asList(getSelection());
				if (sel.contains(rowBeingResized)) {
					// the user double-clicked a row resizer of a selected row
					// so update all selected rows
					for (int cnt = 0; cnt < sel.size(); cnt++)
						((GridItem) sel.get(cnt)).pack();
					redraw();
				} else {
					// otherwise only update the row the user double-clicked
					rowBeingResized.pack();
				}

				resizingRow = false;
				handleHoverOnRowResizer(e.x, e.y);
				return;
			}

			GridItem item = getItem(new Point(e.x, e.y));
			if (item != null) {
				if (isListening(SWT.DefaultSelection)) {
					Event newEvent = new Event();
					newEvent.item = item;

					notifyListeners(SWT.DefaultSelection, newEvent);
				} else if (item.getItemCount() > 0) {
					item.setExpanded(!item.isExpanded());

					if (item.isExpanded()) {
						item.fireEvent(SWT.Expand);
					} else {
						item.fireEvent(SWT.Collapse);
					}
				}
			}
		}
	}

	/**
	 * Mouse up handler.
	 * 
	 * @param e event
	 */
	private void onMouseUp(MouseEvent e) {
		cellSelectedOnLastMouseDown = false;

		if (resizingColumn) {
			resizingColumn = false;
			handleHoverOnColumnResizer(e.x, e.y); // resets cursor if
			// necessary
			return;
		}
		if (resizingRow) {
			resizingRow = false;
			handleHoverOnRowResizer(e.x, e.y); // resets cursor if
			// necessary
			return;
		}

		if (pushingColumn) {
			pushingColumn = false;
			columnBeingPushed.getHeaderRenderer().setMouseDown(false);
			columnBeingPushed.getHeaderRenderer().setHover(false);
			redraw();
			if (pushingAndHovering) {
				columnBeingPushed.fireListeners();
			}
			setCapture(false);
			return;
		}

		if (draggingColumn) {
			handleColumnDrop();
			return;
		}

		if (cellDragSelectionOccuring || cellRowDragSelectionOccuring || cellColumnDragSelectionOccuring) {
			cellDragSelectionOccuring = false;
			cellRowDragSelectionOccuring = false;
			cellColumnDragSelectionOccuring = false;
			setCursor(null);

			if (followupCellSelectionEventOwed) {
				Event se = new Event();
				se.button = e.button;
				se.item = getItem(new Point(e.x, e.y));
				se.stateMask = e.stateMask;
				se.x = e.x;
				se.y = e.y;

				notifyListeners(SWT.Selection, se);
				followupCellSelectionEventOwed = false;
			}
		}
	}

	/**
	 * Mouse move event handler.
	 * 
	 * @param e event
	 */
	private void onMouseMove(MouseEvent e) {
		// check to see if the mouse is outside the grid
		// this should only happen when the mouse is captured for inplace
		// tooltips - see bug 203364
		if (inplaceTooltipCapture && (e.x < 0 || e.y < 0 || e.x >= getBounds().width || e.y >= getBounds().height)) {
			setCapture(false);
			inplaceTooltipCapture = false;
			return; // a mouseexit event should occur immediately
		}

		// if populated will be fired at end of method.
		Event selectionEvent = null;

		if ((e.stateMask & SWT.BUTTON1) == 0) {
			handleHovering(e.x, e.y);
		} else {
			if (draggingColumn) {
				handleColumnDragging(e.x);
				return;
			}

			if (resizingColumn) {
				handleColumnResizerDragging(e.x);
				return;
			}
			if (resizingRow) {
				handleRowResizerDragging(e.y);
				return;
			}
			if (pushingColumn) {
				handleColumnHeaderHoverWhilePushing(e.x, e.y);
				return;
			}
			if (cellSelectionEnabled) {
				if (!cellDragSelectionOccuring && cellSelectedOnLastMouseDown) {
					cellDragSelectionOccuring = true;
					// XXX: make this user definable
					setCursor(getDisplay().getSystemCursor(SWT.CURSOR_CROSS));
					cellDragCTRL = ((e.stateMask & SWT.MOD1) != 0);
					if (cellDragCTRL) {
						selectedCellsBeforeRangeSelect.clear();
						selectedCellsBeforeRangeSelect.addAll(selectedCells);
					}
				}
				if (!cellRowDragSelectionOccuring && cellRowSelectedOnLastMouseDown) {
					cellRowDragSelectionOccuring = true;
					setCursor(getDisplay().getSystemCursor(SWT.CURSOR_CROSS));
					cellDragCTRL = ((e.stateMask & SWT.MOD1) != 0);
					if (cellDragCTRL) {
						selectedCellsBeforeRangeSelect.clear();
						selectedCellsBeforeRangeSelect.addAll(selectedCells);
					}
				}

				if (!cellColumnDragSelectionOccuring && cellColumnSelectedOnLastMouseDown) {
					cellColumnDragSelectionOccuring = true;
					setCursor(getDisplay().getSystemCursor(SWT.CURSOR_CROSS));
					cellDragCTRL = ((e.stateMask & SWT.MOD1) != 0);
					if (cellDragCTRL) {
						selectedCellsBeforeRangeSelect.clear();
						selectedCellsBeforeRangeSelect.addAll(selectedCells);
					}
				}

				int ctrlFlag = (cellDragCTRL ? SWT.MOD1 : SWT.NONE);

				if (cellDragSelectionOccuring && handleCellHover(e.x, e.y)) {
					GridColumn intentColumn = hoveringColumn;
					GridItem intentItem = hoveringItem;

					if (hoveringItem == null) {
						if (e.y > headerHeight) {
							// then we must be hovering way to the bottom
							intentItem = getPreviousVisibleItem(null);
						} else {
							intentItem = (GridItem) items.get(0);
						}
					}

					if (hoveringColumn == null) {
						if (e.x > rowHeaderWidth) {
							// then we must be hovering way to the right
							intentColumn = getVisibleColumn_DegradeLeft(intentItem, (GridColumn) displayOrderedColumns.get(displayOrderedColumns.size() - 1));
						} else {
							GridColumn firstCol = (GridColumn) displayOrderedColumns.get(0);
							if (!firstCol.isVisible()) {
								firstCol = getNextVisibleColumn(firstCol);
							}
							intentColumn = firstCol;
						}
					}

					showColumn(intentColumn);
					showItem(intentItem);
					selectionEvent = updateCellSelection(new Point(indexOf(intentColumn), indexOf(intentItem)), ctrlFlag | SWT.MOD2, true, false);
				}
				if (cellRowDragSelectionOccuring && handleCellHover(e.x, e.y)) {
					GridItem intentItem = hoveringItem;

					if (hoveringItem == null) {
						if (e.y > headerHeight) {
							// then we must be hovering way to the bottom
							intentItem = getPreviousVisibleItem(null);
						} else {
							if (getTopIndex() > 0) {
								intentItem = getPreviousVisibleItem((GridItem) items.get(getTopIndex()));
							} else {
								intentItem = (GridItem) items.get(0);
							}
						}
					}

					Vector cells = new Vector();

					getCells(intentItem, focusItem, cells);

					showItem(intentItem);
					selectionEvent = updateCellSelection(cells, ctrlFlag, true, false);
				}
				if (cellColumnDragSelectionOccuring && handleCellHover(e.x, e.y)) {
					GridColumn intentCol = hoveringColumn;

					if (intentCol == null) {
						if (e.y < rowHeaderWidth) {
							// TODO: get the first col to the left
						} else {
							// TODO: get the first col to the right
						}
					}

					if (intentCol == null) return; // temporary

					GridColumn iterCol = intentCol;

					Vector newSelected = new Vector();

					boolean decreasing = (displayOrderedColumns.indexOf(iterCol) > displayOrderedColumns.indexOf(focusColumn));

					do {
						getCells(iterCol, newSelected);

						if (iterCol == focusColumn) {
							break;
						}

						if (decreasing) {
							iterCol = getPreviousVisibleColumn(iterCol);
						} else {
							iterCol = getNextVisibleColumn(iterCol);
						}

					} while (true);

					selectionEvent = updateCellSelection(newSelected, ctrlFlag, true, false);
				}

			}
		}

		if (selectionEvent != null) {
			selectionEvent.stateMask = e.stateMask;
			selectionEvent.button = e.button;
			selectionEvent.item = getItem(new Point(e.x, e.y));
			selectionEvent.x = e.x;
			selectionEvent.y = e.y;
			notifyListeners(SWT.Selection, selectionEvent);
		}
	}

	/**
	 * Handles the assignment of the correct values to the hover* field variables that let the painting code now what to paint as hovered.
	 * 
	 * @param x mouse x coordinate
	 * @param y mouse y coordinate
	 */
	private void handleHovering(int x, int y) {
		// TODO: need to clean up and refactor hover code
		handleCellHover(x, y);

		// Is this Grid a DragSource ??
		if (cellSelectionEnabled && getData("DragSource") != null) {
			if (handleHoverOnSelectionDragArea(x, y)) {
				return;
			}
		}

		if (columnHeadersVisible) {
			if (handleHoverOnColumnResizer(x, y)) {
				// if (hoveringItem != null || !hoveringDetail.equals("") || hoveringColumn != null
				// || hoveringColumnHeader != null || hoverColumnGroupHeader != null)
				// {
				// hoveringItem = null;
				// hoveringDetail = "";
				// hoveringColumn = null;
				// hoveringColumnHeader = null;
				// hoverColumnGroupHeader = null;
				//
				// Rectangle clientArea = getClientArea();
				// redraw(clientArea.x,clientArea.y,clientArea.width,clientArea.height,false);
				// }
				return;
			}
		}
		if (rowsResizeable && rowHeaderVisible) {
			if (handleHoverOnRowResizer(x, y)) {
				return;
			}
		}

		// handleCellHover(x, y);
	}

	/**
	 * Refreshes the hover* variables according to the mouse location and current state of the table. This is useful is some method call, caused the state of the table to change and therefore the
	 * hover effects may have become out of date.
	 */
	protected void refreshHoverState() {
		Point p = getDisplay().map(null, this, getDisplay().getCursorLocation());
		handleHovering(p.x, p.y);
	}

	/**
	 * Mouse exit event handler.
	 * 
	 * @param e event
	 */
	private void onMouseExit(MouseEvent e) {
		hoveringItem = null;
		hoveringDetail = "";
		hoveringColumn = null;
		hoveringOverText = false;
		hideToolTip();
		redraw();
	}

	/**
	 * Key down event handler.
	 * 
	 * @param e event
	 */
	private void onKeyDown(Event e) {
		if (focusColumn == null || focusColumn.isDisposed()) {
			if (columns.size() == 0) return;

			focusColumn = getColumn(0);
			intendedFocusColumn = focusColumn;
		}

		if (e.character == '\r' && focusItem != null) {
			Event newEvent = new Event();
			newEvent.item = focusItem;

			notifyListeners(SWT.DefaultSelection, newEvent);
			return;
		}

		int attemptExpandCollapse = 0;
		if ((e.character == '-' || (!cellSelectionEnabled && e.keyCode == SWT.ARROW_LEFT)) && focusItem != null && focusItem.isExpanded()) {
			attemptExpandCollapse = SWT.Collapse;
		} else if ((e.character == '+' || (!cellSelectionEnabled && e.keyCode == SWT.ARROW_RIGHT)) && focusItem != null && !focusItem.isExpanded()) {
			attemptExpandCollapse = SWT.Expand;
		}

		if (attemptExpandCollapse != 0 && focusItem != null && focusItem.hasChildren()) {
			int performExpandCollapse = 0;

			if (cellSelectionEnabled && focusColumn != null && focusColumn.isTree()) {
				performExpandCollapse = attemptExpandCollapse;
			} else if (!cellSelectionEnabled) {
				performExpandCollapse = attemptExpandCollapse;
			}

			if (performExpandCollapse == SWT.Expand) {
				focusItem.setExpanded(true);
				focusItem.fireEvent(SWT.Expand);
				return;
			}
			if (performExpandCollapse == SWT.Collapse) {
				focusItem.setExpanded(false);
				focusItem.fireEvent(SWT.Collapse);
				return;
			}
		}

		if (e.character == ' ') {
			handleSpaceBarDown(e);
		}

		GridItem newSelection = null;
		GridColumn newColumnFocus = null;

		// These two variables are used because the key navigation when the shift key is down is
		// based, not off the focus item/column, but rather off the implied focus (i.e. where the
		// keyboard has extended focus to).
		GridItem impliedFocusItem = focusItem;
		GridColumn impliedFocusColumn = focusColumn;

		if (cellSelectionEnabled && e.stateMask == SWT.MOD2) {
			if (shiftSelectionAnchorColumn != null) {
				impliedFocusItem = shiftSelectionAnchorItem;
				impliedFocusColumn = shiftSelectionAnchorColumn;
			}
		}

		switch (e.keyCode) {
		case SWT.ARROW_RIGHT:
			if (cellSelectionEnabled) {
				if (impliedFocusItem != null && impliedFocusColumn != null) {
					newSelection = impliedFocusItem;

					int index = displayOrderedColumns.indexOf(impliedFocusColumn);

					int jumpAhead = impliedFocusItem.getColumnSpan(indexOf(impliedFocusColumn));

					jumpAhead++;

					while (jumpAhead > 0) {
						index++;
						if (index < displayOrderedColumns.size()) {
							if (((GridColumn) displayOrderedColumns.get(index)).isVisible()) jumpAhead--;
						} else {
							break;
						}
					}

					if (index < displayOrderedColumns.size()) {
						newColumnFocus = (GridColumn) displayOrderedColumns.get(index);
					} else {
						newColumnFocus = impliedFocusColumn;
					}
				}
				intendedFocusColumn = newColumnFocus;
			} else {
				if (impliedFocusItem != null && impliedFocusItem.hasChildren()) {
					newSelection = impliedFocusItem.getItem(0);
				}
			}
			break;
		case SWT.ARROW_LEFT:
			if (cellSelectionEnabled) {
				if (impliedFocusItem != null && impliedFocusColumn != null) {
					newSelection = impliedFocusItem;

					int index = displayOrderedColumns.indexOf(impliedFocusColumn);

					if (index != 0) {
						newColumnFocus = (GridColumn) displayOrderedColumns.get(index - 1);

						newColumnFocus = getVisibleColumn_DegradeLeft(impliedFocusItem, newColumnFocus);
					} else {
						newColumnFocus = impliedFocusColumn;
					}
				}
				intendedFocusColumn = newColumnFocus;
			} else {
				if (impliedFocusItem != null && impliedFocusItem.getParentItem() != null) {
					newSelection = impliedFocusItem.getParentItem();
				}
			}
			break;
		case SWT.ARROW_UP:
			if (impliedFocusItem != null) {
				newSelection = getPreviousVisibleItem(impliedFocusItem);
			}

			if (impliedFocusColumn != null) {
				if (newSelection != null) {
					newColumnFocus = getVisibleColumn_DegradeLeft(newSelection, intendedFocusColumn);
				} else {
					newColumnFocus = impliedFocusColumn;
				}
			}

			break;
		case SWT.ARROW_DOWN:
			if (impliedFocusItem != null) {
				newSelection = getNextVisibleItem(impliedFocusItem);
			} else {
				if (items.size() > 0) {
					newSelection = (GridItem) items.get(0);
				}
			}

			if (impliedFocusColumn != null) {
				if (newSelection != null) {
					newColumnFocus = getVisibleColumn_DegradeLeft(newSelection, intendedFocusColumn);
				} else {
					newColumnFocus = impliedFocusColumn;
				}
			}
			break;
		case SWT.HOME:

			if (!cellSelectionEnabled) {
				if (items.size() > 0) {
					newSelection = (GridItem) items.get(0);
				}
			} else {
				newSelection = impliedFocusItem;
				newColumnFocus = getVisibleColumn_DegradeRight(newSelection, (GridColumn) displayOrderedColumns.get(0));
			}

			break;
		case SWT.END:
			if (!cellSelectionEnabled) {
				if (items.size() > 0) {
					newSelection = getPreviousVisibleItem(null);
				}
			} else {
				newSelection = impliedFocusItem;
				newColumnFocus = getVisibleColumn_DegradeLeft(newSelection, (GridColumn) displayOrderedColumns.get(displayOrderedColumns.size() - 1));
			}

			break;
		case SWT.PAGE_UP:
			int topIndex = getTopIndex();

			newSelection = (GridItem) items.get(topIndex);

			if (focusItem == newSelection) {
				RowRange range = getRowRange(getTopIndex(), getVisibleGridHeight(), false, true);
				newSelection = (GridItem) items.get(range.startIndex);
			}

			newColumnFocus = focusColumn;
			break;
		case SWT.PAGE_DOWN:
			int bottomIndex = getBottomIndex();

			newSelection = (GridItem) items.get(bottomIndex);

			if (!isShown(newSelection)) {
				// the item at bottom index is not shown completely
				GridItem tmpItem = getPreviousVisibleItem(newSelection);
				if (tmpItem != null) newSelection = tmpItem;
			}

			if (focusItem == newSelection) {
				RowRange range = getRowRange(getBottomIndex(), getVisibleGridHeight(), true, false);
				newSelection = (GridItem) items.get(range.endIndex);
			}

			newColumnFocus = focusColumn;
			break;
		default:
			break;
		}

		if (newSelection == null) {
			return;
		}

		if (cellSelectionEnabled) {
			if (e.stateMask != SWT.MOD2) focusColumn = newColumnFocus;
			showColumn(newColumnFocus);

			if (e.stateMask != SWT.MOD2) focusItem = newSelection;
			showItem(newSelection);

			if (e.stateMask != SWT.MOD1) {
				Event selEvent = updateCellSelection(new Point(indexOf(newColumnFocus), indexOf(newSelection)), e.stateMask, false, false);
				if (selEvent != null) {
					selEvent.stateMask = e.stateMask;
					selEvent.character = e.character;
					selEvent.keyCode = e.keyCode;
					notifyListeners(SWT.Selection, selEvent);
				}
			}

			redraw();
		} else {
			Event selectionEvent = null;
			if (selectionType == SWT.SINGLE || e.stateMask != SWT.MOD1) {
				selectionEvent = updateSelection(newSelection, e.stateMask);
				if (selectionEvent != null) {
					selectionEvent.stateMask = e.stateMask;
					selectionEvent.character = e.character;
					selectionEvent.keyCode = e.keyCode;
				}
			}

			focusItem = newSelection;
			showItem(newSelection);
			redraw();

			if (selectionEvent != null) notifyListeners(SWT.Selection, selectionEvent);
		}
	}

	private void handleSpaceBarDown(Event event) {
		if (focusItem == null) return;

		if (selectionEnabled && !cellSelectionEnabled && !selectedItems.contains(focusItem)) {
			selectedItems.add(focusItem);
			redraw();
			Event e = new Event();
			e.item = focusItem;
			e.stateMask = event.stateMask;
			e.character = event.character;
			e.keyCode = event.keyCode;
			notifyListeners(SWT.Selection, e);
		}

		if (!cellSelectionEnabled) {
			boolean checkFirstCol = false;
			boolean first = true;

			for (Iterator iter = columns.iterator(); iter.hasNext();) {
				GridColumn col = (GridColumn) iter.next();

				if (first) {
					if (!col.isCheck()) break;

					first = false;
					checkFirstCol = true;
				} else {
					if (col.isCheck()) {
						checkFirstCol = false;
						break;
					}
				}
			}

			if (checkFirstCol) {
				focusItem.setChecked(!focusItem.getChecked());
				redraw();
				focusItem.fireCheckEvent(0);
			}
		}
	}

	/**
	 * Resize event handler.
	 */
	private void onResize() {
		scrollValuesObsolete = true;

		if (topIndex != -1) {
			return;
		}

		topIndex = -1;
		bottomIndex = -1;
	}

	/**
	 * Scrollbar selection event handler.
	 */
	private void onScrollSelection() {
		topIndex = -1;
		bottomIndex = -1;
		refreshHoverState();
		redraw(getClientArea().x, getClientArea().y, getClientArea().width, getClientArea().height, false);
	}

	/**
	 * Returns the intersection of the given column and given item.
	 * 
	 * @param column column
	 * @param item item
	 * @return x,y of top left corner of the cell
	 */
	Point getOrigin(GridColumn column, GridItem item) {
		int x = 0;

		if (rowHeaderVisible) {
			x += rowHeaderWidth;
		}

		x -= getHScrollSelectionInPixels();

		for (Iterator colIterIterator = displayOrderedColumns.iterator(); colIterIterator.hasNext();) {
			GridColumn colIter = (GridColumn) colIterIterator.next();

			if (colIter == column) {
				break;
			}

			if (colIter.isVisible()) {
				x += colIter.getWidth();
			}
		}

		int y = 0;
		if (item != null) {
			if (columnHeadersVisible) {
				y += headerHeight;
			}

			int currIndex = getTopIndex();
			int itemIndex = items.indexOf(item);

			if (itemIndex == -1) {
				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
			}

			while (currIndex != itemIndex) {
				if (currIndex < itemIndex) {
					GridItem currItem = (GridItem) items.get(currIndex);
					if (currItem.isVisible()) {
						y += currItem.getHeight() + 1;
					}
					currIndex++;
				} else if (currIndex > itemIndex) {
					currIndex--;
					GridItem currItem = (GridItem) items.get(currIndex);
					if (currItem.isVisible()) {
						y -= currItem.getHeight() + 1;
					}
				}
			}
		} else {
			if (column.getColumnGroup() != null) {
				y += groupHeaderHeight;
			}
		}

		return new Point(x, y);
	}

	/**
	 * Determines (which cell/if a cell) has been clicked (mouse down really) and notifies the appropriate renderer. Returns true when a cell has responded to this event in some way and prevents the
	 * event from triggering an action further down the chain (like a selection).
	 * 
	 * @param item item clicked
	 * @param x mouse x
	 * @param y mouse y
	 * @return true if this event has been consumed.
	 */
	private boolean handleCellClick(GridItem item, int x, int y) {

		// if(!isTree)
		// return false;

		GridColumn col = getColumn(new Point(x, y));
		if (col == null) {
			return false;
		}

		col.getCellRenderer().setBounds(item.getBounds(indexOf(col)));
		return col.getCellRenderer().notify(IInternalWidget.LeftMouseButtonDown, new Point(x, y), item);

	}

	/**
	 * Sets the hovering variables (hoverItem,hoveringColumn) as well as hoverDetail by talking to the cell renderers. Triggers a redraw if necessary.
	 * 
	 * @param x mouse x
	 * @param y mouse y
	 * @return true if a new section of the table is now being hovered
	 */
	private boolean handleCellHover(int x, int y) {

		String detail = "";

		boolean overText = false;

		final GridColumn col = getColumn(new Point(x, y));
		final GridItem item = getItem(new Point(x, y));

		GridColumnGroup hoverColGroup = null;
		GridColumn hoverColHeader = null;

		if (col != null) {
			if (item != null) {
				if (y < getClientArea().height - footerHeight) {
					col.getCellRenderer().setBounds(item.getBounds(columns.indexOf(col)));

					if (col.getCellRenderer().notify(IInternalWidget.MouseMove, new Point(x, y), item)) {
						detail = col.getCellRenderer().getHoverDetail();
					}

					Rectangle textBounds = col.getCellRenderer().getTextBounds(item, false);

					if (textBounds != null) {
						Point p = new Point(x - col.getCellRenderer().getBounds().x, y - col.getCellRenderer().getBounds().y);
						overText = textBounds.contains(p);
					}
				}
			} else {
				if (y < headerHeight) {
					if (columnGroups.length != 0 && y < groupHeaderHeight && col.getColumnGroup() != null) {
						hoverColGroup = col.getColumnGroup();
						hoverColGroup.getHeaderRenderer().setBounds(hoverColGroup.getBounds());
						if (hoverColGroup.getHeaderRenderer().notify(IInternalWidget.MouseMove, new Point(x, y), hoverColGroup)) {
							detail = hoverColGroup.getHeaderRenderer().getHoverDetail();
						}

						Rectangle textBounds = hoverColGroup.getHeaderRenderer().getTextBounds(hoverColGroup, false);

						if (textBounds != null) {
							Point p = new Point(x - hoverColGroup.getHeaderRenderer().getBounds().x, y - hoverColGroup.getHeaderRenderer().getBounds().y);
							overText = textBounds.contains(p);
						}
					} else {
						// on col header
						hoverColHeader = col;

						col.getHeaderRenderer().setBounds(col.getBounds());
						if (col.getHeaderRenderer().notify(IInternalWidget.MouseMove, new Point(x, y), col)) {
							detail = col.getHeaderRenderer().getHoverDetail();
						}

						Rectangle textBounds = col.getHeaderRenderer().getTextBounds(col, false);

						if (textBounds != null) {
							Point p = new Point(x - col.getHeaderRenderer().getBounds().x, y - col.getHeaderRenderer().getBounds().y);
							overText = textBounds.contains(p);
						}
					}
				}
			}
		}

		boolean hoverChange = false;

		if (hoveringItem != item || !hoveringDetail.equals(detail) || hoveringColumn != col || hoverColGroup != hoverColumnGroupHeader || hoverColHeader != hoveringColumnHeader) {
			hoveringItem = item;
			hoveringDetail = detail;
			hoveringColumn = col;
			hoveringColumnHeader = hoverColHeader;
			hoverColumnGroupHeader = hoverColGroup;

			Rectangle clientArea = getClientArea();
			redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);

			hoverChange = true;
		}

		// do inplace toolTip stuff
		if (hoverChange || hoveringOverText != overText) {
			hoveringOverText = overText;

			if (overText) {

				Rectangle cellBounds = null;
				Rectangle textBounds = null;
				Rectangle preferredTextBounds = null;

				if (hoveringItem != null && hoveringItem.getToolTipText(indexOf(col)) == null && // no inplace tooltips when regular tooltip
						!col.getWordWrap()) // dont show inplace tooltips for cells with wordwrap
				{
					cellBounds = col.getCellRenderer().getBounds();
					if (cellBounds.x + cellBounds.width > getSize().x) {
						cellBounds.width = getSize().x - cellBounds.x;
					}
					textBounds = col.getCellRenderer().getTextBounds(item, false);
					preferredTextBounds = col.getCellRenderer().getTextBounds(item, true);
				} else if (hoveringColumnHeader != null && hoveringColumnHeader.getHeaderTooltip() == null) // no inplace tooltips when regular tooltip
				{
					cellBounds = hoveringColumnHeader.getHeaderRenderer().getBounds();
					if (cellBounds.x + cellBounds.width > getSize().x) {
						cellBounds.width = getSize().x - cellBounds.x;
					}
					textBounds = hoveringColumnHeader.getHeaderRenderer().getTextBounds(col, false);
					preferredTextBounds = hoveringColumnHeader.getHeaderRenderer().getTextBounds(col, true);
				} else if (hoverColumnGroupHeader != null) {
					cellBounds = hoverColumnGroupHeader.getHeaderRenderer().getBounds();
					if (cellBounds.x + cellBounds.width > getSize().x) {
						cellBounds.width = getSize().x - cellBounds.x;
					}
					textBounds = hoverColumnGroupHeader.getHeaderRenderer().getTextBounds(hoverColumnGroupHeader, false);
					preferredTextBounds = hoverColumnGroupHeader.getHeaderRenderer().getTextBounds(hoverColumnGroupHeader, true);
				}

				// if we are truncated
				if (textBounds != null && textBounds.width < preferredTextBounds.width) {
					showToolTip(item, col, hoverColumnGroupHeader, new Point(cellBounds.x + textBounds.x, cellBounds.y + textBounds.y));
					// the following 2 lines are done here rather than in showToolTip to allow
					// that method to be overridden yet still capture the mouse.
					setCapture(true);
					inplaceTooltipCapture = true;
				}
			} else {
				hideToolTip();
			}
		}

		// do normal cell specific tooltip stuff
		if (hoverChange) {
			String newTip = null;
			if ((hoveringItem != null) && (hoveringColumn != null)) {
				// get cell specific tooltip
				newTip = hoveringItem.getToolTipText(indexOf(hoveringColumn));
			} else if ((hoveringColumn != null) && (hoveringColumnHeader != null)) {
				// get column header specific tooltip
				newTip = hoveringColumn.getHeaderTooltip();
			}

			if (newTip == null) { // no cell or column header specific tooltip then use base Grid tooltip
				newTip = getToolTipText();
			}

			// Avoid unnecessarily resetting tooltip - this will cause the tooltip to jump around
			if (newTip != null && !newTip.equals(displayedToolTipText)) {
				updateToolTipText(newTip);
			} else if (newTip == null && displayedToolTipText != null) {
				updateToolTipText(null);
			}
			displayedToolTipText = newTip;
		}

		return hoverChange;
	}

	/**
	 * Sets the tooltip for the whole Grid to the given text. This method is made available for subclasses to override, when a subclass wants to display a different than the standard SWT/OS tooltip.
	 * Generally, those subclasses would override this event and use this tooltip text in their own tooltip or just override this method to prevent the SWT/OS tooltip from displaying.
	 * 
	 * @param text
	 */
	protected void updateToolTipText(String text) {
		super.setToolTipText(text);
	}

	/**
	 * Marks the scroll values obsolete so they will be recalculated.
	 */
	protected void setScrollValuesObsolete() {
		this.scrollValuesObsolete = true;
		redraw();
	}

	/**
	 * Inserts a new column into the table.
	 * 
	 * @param column new column
	 * @param index index to insert new column
	 * @return current number of columns
	 */
	int newColumn(GridColumn column, int index) {

		if (index == -1) {
			columns.add(column);
			displayOrderedColumns.add(column);
		} else {
			columns.add(index, column);
			displayOrderedColumns.add(index, column);

			for (int i = 0; i < columns.size(); i++) {
				((GridColumn) columns.get(i)).setColumnIndex(i);
			}
		}

		computeHeaderHeight(sizingGC);
		computeFooterHeight(sizingGC);

		updatePrimaryCheckColumn();

		for (Iterator iterator = items.iterator(); iterator.hasNext();) {
			GridItem item = (GridItem) iterator.next();
			item.columnAdded(index);
		}

		scrollValuesObsolete = true;
		redraw();

		return columns.size() - 1;
	}

	/**
	 * Removes the given column from the table.
	 * 
	 * @param column column to remove
	 */
	void removeColumn(GridColumn column) {
		boolean selectionModified = false;

		int index = indexOf(column);

		if (cellSelectionEnabled) {
			Vector removeSelectedCells = new Vector();

			for (Iterator iterator = selectedCells.iterator(); iterator.hasNext();) {
				Point cell = (Point) iterator.next();
				if (cell.x == index) {
					removeSelectedCells.add(cell);
				}
			}

			if (removeSelectedCells.size() > 0) {
				selectedCells.removeAll(removeSelectedCells);
				selectionModified = true;
			}

			for (Iterator iterator = selectedCells.iterator(); iterator.hasNext();) {
				Point cell = (Point) iterator.next();
				if (cell.x >= index) {
					cell.x--;
					selectionModified = true;
				}
			}
		}

		columns.remove(column);
		displayOrderedColumns.remove(column);

		updatePrimaryCheckColumn();

		scrollValuesObsolete = true;
		redraw();

		for (Iterator iterator = items.iterator(); iterator.hasNext();) {
			GridItem item = (GridItem) iterator.next();
			item.columnRemoved(index);
		}

		int i = 0;
		for (Iterator iterator = columns.iterator(); iterator.hasNext();) {
			GridColumn col = (GridColumn) iterator.next();
			col.setColumnIndex(i);
			i++;
		}

		if (selectionModified && !disposing) {
			updateColumnSelection();
		}
	}

	/**
	 * Manages the setting of the checkbox column when the SWT.CHECK style was given to the table. This method will ensure that the first column of the table always has a checkbox when SWT.CHECK is
	 * given to the table.
	 */
	private void updatePrimaryCheckColumn() {
		if ((getStyle() & SWT.CHECK) == SWT.CHECK) {
			boolean firstCol = true;

			for (Iterator iter = columns.iterator(); iter.hasNext();) {
				GridColumn col = (GridColumn) iter.next();
				col.setTableCheck(firstCol);
				firstCol = false;
			}
		}
	}

	void newRootItem(GridItem item, int index) {
		if (index == -1 || index >= rootItems.size()) {
			rootItems.add(item);
		} else {
			rootItems.add(index, item);
		}
	}

	void removeRootItem(GridItem item) {
		rootItems.remove(item);
	}

	/**
	 * Creates the new item at the given index. Only called from GridItem constructor.
	 * 
	 * @param item new item
	 * @param index index to insert the item at
	 * @return the index where the item was insert
	 */
	int newItem(GridItem item, int index, boolean root) {
		int row = 0;

		if (!isTree) {
			if (item.getParentItem() != null) {
				isTree = true;
			}
		}

		// Have to convert indexes, this method needs a flat index, the method is called with indexes
		// that are relative to the level
		if (root && index != -1) {
			if (index >= rootItems.size()) {
				index = -1;
			} else {
				index = items.indexOf(rootItems.get(index));
			}
		} else if (!root) {
			if (index >= item.getParentItem().getItems().length || index == -1) {
				GridItem rightMostDescendent = item.getParentItem();

				while (rightMostDescendent.getItems().length > 0) {
					rightMostDescendent = rightMostDescendent.getItems()[rightMostDescendent.getItems().length - 1];
				}

				index = indexOf(rightMostDescendent) + 1;
			} else {
				index = indexOf(item.getParentItem().getItems()[index]);
			}
		}

		if (index == -1) {
			items.add(item);
			row = items.size() - 1;
		} else {
			items.add(index, item);
			row = index;
		}

		if (items.size() == 1 && !userModifiedItemHeight) itemHeight = computeItemHeight(item, sizingGC);

		item.initializeHeight(itemHeight);

		if (isRowHeaderVisible() && isAutoWidth()) {
			rowHeaderWidth = Math.max(rowHeaderWidth, rowHeaderRenderer.computeSize(sizingGC, SWT.DEFAULT, SWT.DEFAULT, item).x);
		}

		scrollValuesObsolete = true;
		topIndex = -1;
		bottomIndex = -1;

		currentVisibleItems++;

		redraw();

		return row;
	}

	/**
	 * Removes the given item from the table. This method is only called from the item's dispose method.
	 * 
	 * @param item item to remove
	 */
	void removeItem(GridItem item) {
		Point[] cells = getCells(item);
		boolean selectionModified = false;

		items.remove(item);

		if (disposing) return;

		if (selectedItems.remove(item)) selectionModified = true;

		for (int i = 0; i < cells.length; i++) {
			if (selectedCells.remove(cells[i])) selectionModified = true;
		}

		if (focusItem == item) {
			focusItem = null;
		}

		scrollValuesObsolete = true;
		topIndex = -1;
		bottomIndex = -1;
		if (item.isVisible()) {
			currentVisibleItems--;
		}

		if (selectionModified && !disposing) {
			updateColumnSelection();
		}

		redraw();
		// Need to update the scrollbars see see 375327
		updateScrollbars();
	}

	/**
	 * Creates the given column group at the given index. This method is only called from the {@code GridColumnGroup}'s constructor.
	 * 
	 * @param group group to add.
	 */
	void newColumnGroup(GridColumnGroup group) {
		GridColumnGroup[] newColumnGroups = new GridColumnGroup[columnGroups.length + 1];
		System.arraycopy(columnGroups, 0, newColumnGroups, 0, columnGroups.length);
		newColumnGroups[newColumnGroups.length - 1] = group;
		columnGroups = newColumnGroups;

		// if we just added the first col group, then we need to up the row
		// height
		if (columnGroups.length == 1) {
			computeHeaderHeight(sizingGC);
		}

		scrollValuesObsolete = true;
		redraw();
	}

	/**
	 * Removes the given column group from the table. This method is only called from the {@code GridColumnGroup}'s dispose method.
	 * 
	 * @param group group to remove.
	 */
	void removeColumnGroup(GridColumnGroup group) {
		GridColumnGroup[] newColumnGroups = new GridColumnGroup[columnGroups.length - 1];
		int newIndex = 0;
		for (int i = 0; i < columnGroups.length; i++) {
			if (columnGroups[i] != group) {
				newColumnGroups[newIndex] = columnGroups[i];
				newIndex++;
			}
		}
		columnGroups = newColumnGroups;

		if (columnGroups.length == 0) {
			computeHeaderHeight(sizingGC);
		}

		scrollValuesObsolete = true;
		redraw();
	}

	/**
	 * Updates the cached number of visible items by the given amount.
	 * 
	 * @param amount amount to update cached total
	 */
	void updateVisibleItems(int amount) {
		currentVisibleItems += amount;
	}

	/**
	 * Returns the current item in focus.
	 * 
	 * @return item in focus or {@code null}.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public GridItem getFocusItem() {
		checkWidget();
		return focusItem;
	}

	/**
	 * Returns the current cell in focus. If cell selection is disabled, this method returns null.
	 * 
	 * @return cell in focus or {@code null}. x represents the column and y the row the cell is in
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public Point getFocusCell() {
		checkWidget();
		if (!cellSelectionEnabled) return null;

		int x = -1;
		int y = -1;

		if (focusColumn != null) x = indexOf(focusColumn);

		if (focusItem != null) y = indexOf(focusItem);

		return new Point(x, y);
	}

	/**
	 * Sets the focused item to the given item.
	 * 
	 * @param item item to focus.
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_ARGUMENT - if item is disposed</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setFocusItem(GridItem item) {
		checkWidget();
		// TODO: check and make sure this item is valid for focus
		if (item == null || item.isDisposed() || item.getParent() != this) {
			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}
		focusItem = item;
	}

	/**
	 * Sets the focused item to the given column. Column focus is only applicable when cell selection is enabled.
	 * 
	 * @param column column to focus.
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_INVALID_ARGUMENT - if item is disposed</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setFocusColumn(GridColumn column) {
		checkWidget();
		// TODO: check and make sure this item is valid for focus
		if (column == null || column.isDisposed() || column.getParent() != this || !column.isVisible()) {
			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}

		focusColumn = column;
		intendedFocusColumn = column;
	}

	/**
	 * Returns an array of the columns in their display order.
	 * 
	 * @return columns in display order
	 */
	GridColumn[] getColumnsInOrder() {
		checkWidget();
		return (GridColumn[]) displayOrderedColumns.toArray(new GridColumn[columns.size()]);
	}

	/**
	 * Returns true if the table is set to horizontally scroll column-by-column rather than pixel-by-pixel.
	 * 
	 * @return true if the table is scrolled horizontally by column
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean getColumnScrolling() {
		checkWidget();
		return columnScrolling;
	}

	/**
	 * Sets the table scrolling method to either scroll column-by-column (true) or pixel-by-pixel (false).
	 * 
	 * @param columnScrolling true to horizontally scroll by column, false to scroll by pixel
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setColumnScrolling(boolean columnScrolling) {
		checkWidget();
		if (rowHeaderVisible && !columnScrolling) {
			return;
		}

		this.columnScrolling = columnScrolling;
		scrollValuesObsolete = true;
		redraw();
	}

	/**
	 * Returns the first visible column that is not spanned by any other column that is either the given column or any of the columns displaying to the left of the given column. If the given column
	 * and subsequent columns to the right are either not visible or spanned, this method will return null.
	 * 
	 * @param item
	 * @param col
	 * @return
	 */
	private GridColumn getVisibleColumn_DegradeLeft(GridItem item, GridColumn col) {
		int index = displayOrderedColumns.indexOf(col);

		GridColumn prevCol = col;

		int i = 0;
		while (!prevCol.isVisible()) {
			i++;
			if (index - i < 0) return null;

			prevCol = (GridColumn) displayOrderedColumns.get(index - i);
		}

		index = displayOrderedColumns.indexOf(prevCol);

		for (int j = 0; j < index; j++) {
			GridColumn tempCol = (GridColumn) displayOrderedColumns.get(j);

			if (!tempCol.isVisible()) {
				continue;
			}

			if (item.getColumnSpan(indexOf(tempCol)) >= index - j) {
				prevCol = tempCol;
				break;
			}
		}

		return prevCol;
	}

	/**
	 * Returns the first visible column that is not spanned by any other column that is either the given column or any of the columns displaying to the right of the given column. If the given column
	 * and subsequent columns to the right are either not visible or spanned, this method will return null.
	 * 
	 * @param item
	 * @param col
	 * @return
	 */
	private GridColumn getVisibleColumn_DegradeRight(GridItem item, GridColumn col) {
		int index = displayOrderedColumns.indexOf(col);

		int i = 0;
		GridColumn nextCol = col;
		while (!nextCol.isVisible()) {
			i++;
			if (index + i == displayOrderedColumns.size()) return null;

			nextCol = (GridColumn) displayOrderedColumns.get(index + i);
		}

		index = displayOrderedColumns.indexOf(nextCol);
		int startIndex = index;

		while (index > 0) {

			index--;
			GridColumn prevCol = (GridColumn) displayOrderedColumns.get(index);

			if (item.getColumnSpan(indexOf(prevCol)) >= startIndex - index) {
				if (startIndex == displayOrderedColumns.size() - 1) {
					return null;
				} else {
					return getVisibleColumn_DegradeRight(item, (GridColumn) displayOrderedColumns.get(startIndex + 1));
				}
			}

		}

		return nextCol;
	}

	/**
	 * Returns true if the cells are selectable in the reciever.
	 * 
	 * @return cell selection enablement status.
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public boolean getCellSelectionEnabled() {
		checkWidget();
		return cellSelectionEnabled;
	}

	/**
	 * Sets whether cells are selectable in the receiver.
	 * 
	 * @param cellSelection the cellSelection to set
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setCellSelectionEnabled(boolean cellSelection) {
		checkWidget();
		if (!cellSelection) {
			selectedCells.clear();
			redraw();
		} else {
			selectedItems.clear();
			redraw();
		}

		this.cellSelectionEnabled = cellSelection;
	}

	/**
	 * @return <code>true</code> if cell selection is enabled
	 */
	public boolean isCellSelectionEnabled() {
		return cellSelectionEnabled;
	}

	/**
	 * Deselects the given cell in the receiver. If the given cell is already deselected it remains deselected. Invalid cells are ignored.
	 * 
	 * @param cell cell to deselect.
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the cell is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void deselectCell(Point cell) {
		checkWidget();

		if (cell == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		selectedCells.remove(cell);
		updateColumnSelection();
		redraw();
	}

	/**
	 * Deselects the given cells. Invalid cells are ignored.
	 * 
	 * @param cells the cells to deselect.
	 * 
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the set of cells or any cell is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void deselectCells(Point[] cells) {
		checkWidget();

		if (cells == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		for (int i = 0; i < cells.length; i++) {
			if (cells[i] == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		for (int i = 0; i < cells.length; i++) {
			selectedCells.remove(cells[i]);
		}

		updateColumnSelection();

		redraw();
	}

	/**
	 * Deselects all selected cells in the receiver.
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void deselectAllCells() {
		checkWidget();
		selectedCells.clear();
		updateColumnSelection();
		redraw();
	}

	/**
	 * Selects the given cell. Invalid cells are ignored.
	 * 
	 * @param cell point whose x values is a column index and y value is an item index
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the item is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void selectCell(Point cell) {
		checkWidget();

		if (!cellSelectionEnabled) return;

		if (cell == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		addToCellSelection(cell);
		updateColumnSelection();
		redraw();
	}

	/**
	 * Selects the given cells. Invalid cells are ignored.
	 * 
	 * @param cells an arry of points whose x value is a column index and y value is an item index
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the set of cells or an individual cell is null</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void selectCells(Point[] cells) {
		checkWidget();

		if (!cellSelectionEnabled) return;

		if (cells == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		for (int i = 0; i < cells.length; i++) {
			if (cells[i] == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}

		for (int i = 0; i < cells.length; i++) {
			addToCellSelection(cells[i]);
		}

		updateColumnSelection();
		redraw();
	}

	/**
	 * Selects all cells in the receiver.
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void selectAllCells() {
		checkWidget();
		selectAllCellsInternal();
	}

	/**
	 * Selects all cells in the receiver.
	 * 
	 * @return An Event object
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	private Event selectAllCellsInternal() {
		if (!cellSelectionEnabled) return selectAllRowsInternal();

		if (columns.size() == 0) return null;

		if (items.size() == 0) return null;

		int index = 0;
		GridColumn column = (GridColumn) displayOrderedColumns.get(index);

		while (!column.isVisible()) {
			index++;

			if (index >= columns.size()) return null;

			column = (GridColumn) displayOrderedColumns.get(index);
		}

		GridColumn oldFocusColumn = focusColumn;
		GridItem oldFocusItem = focusItem;

		focusColumn = column;
		focusItem = (GridItem) items.get(0);

		GridItem lastItem = getPreviousVisibleItem(null);
		GridColumn lastCol = getVisibleColumn_DegradeLeft(lastItem, (GridColumn) displayOrderedColumns.get(displayOrderedColumns.size() - 1));

		Event event = updateCellSelection(new Point(indexOf(lastCol), indexOf(lastItem)), SWT.MOD2, true, false);

		focusColumn = oldFocusColumn;
		focusItem = oldFocusItem;

		updateColumnSelection();

		redraw();
		return event;
	}

	/**
	 * Selects rows in the receiver.
	 * 
	 * @return An Event object
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	private Event selectAllRowsInternal() {
		if (cellSelectionEnabled) return selectAllCellsInternal();

		if (SWT.MULTI != selectionType) return null;

		if (items.size() == 0) return null;

		deselectAll();

		GridItem oldFocusItem = focusItem;

		GridItem firstItem = getItem(getTopIndex());
		GridItem lastItem = getPreviousVisibleItem(null);

		setFocusItem(firstItem);
		updateSelection(firstItem, SWT.NONE);

		Event event = updateSelection(lastItem, SWT.MOD2);

		setFocusItem(oldFocusItem);

		redraw();
		return event;
	}

	/**
	 * Selects all cells in the given column in the receiver.
	 * 
	 * @param col
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void selectColumn(int col) {
		checkWidget();
		Vector cells = new Vector();
		getCells(getColumn(col), cells);
		selectCells((Point[]) cells.toArray(new Point[0]));
	}

	/**
	 * Selects all cells in the given column group in the receiver.
	 * 
	 * @param colGroup the column group
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void selectColumnGroup(int colGroup) {
		selectColumnGroup(getColumnGroup(colGroup));
	}

	/**
	 * Selects all cells in the given column group in the receiver.
	 * 
	 * @param colGroup the column group
	 * 
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void selectColumnGroup(GridColumnGroup colGroup) {
		checkWidget();
		Vector cells = new Vector();
		getCells(colGroup, cells);
		selectCells((Point[]) cells.toArray(new Point[0]));
	}

	/**
	 * Selects the selection to the given cell. The existing selection is cleared before selecting the given cell.
	 * 
	 * @param cell point whose x values is a column index and y value is an item index
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the item is null</li>
	 *             <li>ERROR_INVALID_ARGUMENT - if the cell is invalid</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setCellSelection(Point cell) {
		checkWidget();

		if (!cellSelectionEnabled) return;

		if (cell == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		if (!isValidCell(cell)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);

		selectedCells.clear();
		addToCellSelection(cell);
		updateColumnSelection();
		redraw();
	}

	/**
	 * Selects the selection to the given set of cell. The existing selection is cleared before selecting the given cells.
	 * 
	 * @param cells point array whose x values is a column index and y value is an item index
	 * @throws IllegalArgumentException <ul>
	 *             <li>ERROR_NULL_ARGUMENT - if the cell array or an individual cell is null</li>
	 *             <li>ERROR_INVALID_ARGUMENT - if the a cell is invalid</li>
	 *             </ul>
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public void setCellSelection(Point[] cells) {
		checkWidget();

		if (!cellSelectionEnabled) return;

		if (cells == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

		for (int i = 0; i < cells.length; i++) {
			if (cells[i] == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);

			if (!isValidCell(cells[i])) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}

		selectedCells.clear();
		for (int i = 0; i < cells.length; i++) {
			addToCellSelection(cells[i]);
		}

		updateColumnSelection();
		redraw();
	}

	/**
	 * Returns an array of cells that are currently selected in the receiver. The order of the items is unspecified. An empty array indicates that no items are selected.
	 * <p>
	 * Note: This is not the actual structure used by the receiver to maintain its selection, so modifying the array will not affect the receiver.
	 * </p>
	 * 
	 * @return an array representing the cell selection
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public Point[] getCellSelection() {
		checkWidget();
		return (Point[]) selectedCells.toArray(new Point[selectedCells.size()]);
	}

	GridColumn getFocusColumn() {
		return focusColumn;
	}

	void updateColumnFocus() {
		if (!focusColumn.isVisible()) {
			int index = displayOrderedColumns.indexOf(focusColumn);
			if (index > 0) {
				GridColumn prev = (GridColumn) displayOrderedColumns.get(index - 1);
				prev = getVisibleColumn_DegradeLeft(focusItem, prev);
				if (prev == null) {
					prev = getVisibleColumn_DegradeRight(focusItem, focusColumn);
				}
				focusColumn = prev;
			} else {
				focusColumn = getVisibleColumn_DegradeRight(focusItem, focusColumn);
			}
		}
	}

	private void getCells(GridColumn col, Vector cells) {
		int colIndex = indexOf(col);

		int columnAtPosition = 0;
		for (Iterator iter = displayOrderedColumns.iterator(); iter.hasNext();) {
			GridColumn nextCol = (GridColumn) iter.next();
			if (!nextCol.isVisible()) continue;

			if (nextCol == col) break;

			columnAtPosition++;
		}

		GridItem item = null;
		if (getItemCount() > 0) item = getItem(0);

		while (item != null) {
			// is cell spanned
			int position = -1;
			boolean spanned = false;
			for (Iterator iter = displayOrderedColumns.iterator(); iter.hasNext();) {
				GridColumn nextCol = (GridColumn) iter.next();
				if (!nextCol.isVisible()) continue;

				if (nextCol == col) break;

				int span = item.getColumnSpan(indexOf(nextCol));

				if (position + span >= columnAtPosition) {
					spanned = true;
					break;
				}
			}

			if (!spanned && item.getColumnSpan(colIndex) == 0) {
				cells.add(new Point(colIndex, indexOf(item)));
			}

			item = getNextVisibleItem(item);
		}
	}

	private void getCells(GridColumnGroup colGroup, Vector cells) {
		GridColumn[] cols = colGroup.getColumns();
		for (int i = 0; i < cols.length; i++) {
			getCells(cols[i], cells);
		}
	}

	private void getCells(GridItem item, Vector cells) {
		int itemIndex = indexOf(item);

		int span = 0;

		for (Iterator iter = displayOrderedColumns.iterator(); iter.hasNext();) {
			GridColumn nextCol = (GridColumn) iter.next();

			if (span > 0) {
				span--;
				continue;
			}

			if (!nextCol.isVisible()) continue;

			span = item.getColumnSpan(indexOf(nextCol));

			cells.add(new Point(indexOf(nextCol), itemIndex));
		}
	}

	private Point[] getCells(GridItem item) {
		Vector cells = new Vector();

		int itemIndex = indexOf(item);

		int span = 0;

		for (Iterator iter = displayOrderedColumns.iterator(); iter.hasNext();) {
			GridColumn nextCol = (GridColumn) iter.next();

			if (span > 0) {
				span--;
				continue;
			}

			if (!nextCol.isVisible()) continue;

			span = item.getColumnSpan(indexOf(nextCol));

			cells.add(new Point(indexOf(nextCol), itemIndex));
		}
		return (Point[]) cells.toArray(new Point[] {});
	}

	private void getCells(GridItem fromItem, GridItem toItem, Vector cells) {
		boolean descending = (indexOf(fromItem) < indexOf(toItem));

		GridItem iterItem = toItem;

		do {
			getCells(iterItem, cells);

			if (iterItem == fromItem) break;

			if (descending) {
				iterItem = getPreviousVisibleItem(iterItem);
			} else {
				iterItem = getNextVisibleItem(iterItem);
			}
		} while (true);
	}

	private int blend(int v1, int v2, int ratio) {
		return (ratio * v1 + (100 - ratio) * v2) / 100;
	}

	private RGB blend(RGB c1, RGB c2, int ratio) {
		int r = blend(c1.red, c2.red, ratio);
		int g = blend(c1.green, c2.green, ratio);
		int b = blend(c1.blue, c2.blue, ratio);
		return new RGB(r, g, b);
	}

	/**
	 * Returns a point whose x and y values are the to and from column indexes of the new selection range inclusive of all spanned columns.
	 * 
	 * @param fromItem
	 * @param fromColumn
	 * @param toItem
	 * @param toColumn
	 * @return
	 */
	private Point getSelectionRange(GridItem fromItem, GridColumn fromColumn, GridItem toItem, GridColumn toColumn) {
		if (displayOrderedColumns.indexOf(fromColumn) > displayOrderedColumns.indexOf(toColumn)) {
			GridColumn temp = fromColumn;
			fromColumn = toColumn;
			toColumn = temp;
		}

		if (indexOf(fromItem) > indexOf(toItem)) {
			GridItem temp = fromItem;
			fromItem = toItem;
			toItem = temp;
		}

		boolean firstTime = true;
		GridItem iterItem = fromItem;

		int fromIndex = indexOf(fromColumn);
		int toIndex = indexOf(toColumn);

		do {
			if (!firstTime) {
				iterItem = getNextVisibleItem(iterItem);
			} else {
				firstTime = false;
			}

			Point cols = getRowSelectionRange(iterItem, fromColumn, toColumn);

			// check and see if column spanning means that the range increased
			if (cols.x != fromIndex || cols.y != toIndex) {
				GridColumn newFrom = getColumn(cols.x);
				GridColumn newTo = getColumn(cols.y);

				// Unfortunately we have to start all over again from the top with the new range
				return getSelectionRange(fromItem, newFrom, toItem, newTo);
			}
		} while (iterItem != toItem);

		return new Point(indexOf(fromColumn), indexOf(toColumn));
	}

	/**
	 * Returns a point whose x and y value are the to and from column indexes of the new selection range inclusive of all spanned columns.
	 * 
	 * @param item
	 * @param fromColumn
	 * @param toColumn
	 * @return
	 */
	private Point getRowSelectionRange(GridItem item, GridColumn fromColumn, GridColumn toColumn) {
		int newFrom = indexOf(fromColumn);
		int newTo = indexOf(toColumn);

		int span = 0;
		int spanningColIndex = -1;
		boolean spanningBeyondToCol = false;

		for (Iterator iter = displayOrderedColumns.iterator(); iter.hasNext();) {
			GridColumn col = (GridColumn) iter.next();

			if (!col.isVisible()) {
				if (span > 0) span--;
				continue;
			}

			if (span > 0) {
				if (col == fromColumn) {
					newFrom = spanningColIndex;
				} else if (col == toColumn && span > 1) {
					spanningBeyondToCol = true;
				}

				span--;

				if (spanningBeyondToCol && span == 0) {
					newTo = indexOf(col);
					break;
				}
			} else {
				int index = indexOf(col);
				span = item.getColumnSpan(index);
				if (span > 0) spanningColIndex = index;

				if (col == toColumn && span > 0) spanningBeyondToCol = true;
			}

			if (col == toColumn && !spanningBeyondToCol) break;

		}

		return new Point(newFrom, newTo);
	}

	/**
	 * Returns the column which is spanning the given column for the given item or null if it is not being spanned.
	 * 
	 * @param item
	 * @param column
	 * @return
	 */
	private GridColumn getSpanningColumn(GridItem item, GridColumn column) {
		int span = 0;
		GridColumn spanningCol = null;

		for (Iterator iter = displayOrderedColumns.iterator(); iter.hasNext();) {
			GridColumn col = (GridColumn) iter.next();

			if (col == column) {
				return spanningCol;
			}

			if (span > 0) {
				span--;
				if (span == 0) spanningCol = null;
			} else {
				int index = indexOf(col);
				span = item.getColumnSpan(index);

				if (span > 0) spanningCol = col;
			}
		}
		return spanningCol;
	}

	/**
	 * Returns true if the given cell's x and y values are valid column and item indexes respectively.
	 * 
	 * @param cell
	 * @return
	 */
	private boolean isValidCell(Point cell) {
		if (cell.x < 0 || cell.x >= columns.size()) return false;

		if (cell.y < 0 || cell.y >= items.size()) return false;

		return true;
	}

	/**
	 * Shows the inplace tooltip for the given item and column. The location is the x and y origin of the text in the cell.
	 * <p>
	 * This method may be overriden to provide their own custom tooltips.
	 * 
	 * @param item the item currently hovered over or null.
	 * @param column the column currently hovered over or null.
	 * @param group the group currently hovered over or null.
	 * @param location the x,y origin of the text in the hovered object.
	 */
	protected void showToolTip(GridItem item, GridColumn column, GridColumnGroup group, Point location) {
		if (inplaceToolTip == null) {
			inplaceToolTip = new GridToolTip(this);
		}

		if (group != null) {
			inplaceToolTip.setFont(getFont());
			inplaceToolTip.setText(group.getText());
		} else if (item != null) {
			inplaceToolTip.setFont(item.getFont(item.getParent().indexOf(column)));
			inplaceToolTip.setText(item.getText(item.getParent().indexOf(column)));
		} else if (column != null) {
			inplaceToolTip.setFont(getFont());
			inplaceToolTip.setText(column.getText());
		}

		Point p = getDisplay().map(this, null, location);

		inplaceToolTip.setLocation(p);

		inplaceToolTip.setVisible(true);
	}

	/**
	 * Hides the inplace tooltip.
	 * <p>
	 * This method must be overriden when showToolTip is overriden. Subclasses must call super when overriding this method.
	 */
	protected void hideToolTip() {
		if (inplaceToolTip != null) {
			inplaceToolTip.setVisible(false);
		}
		if (inplaceTooltipCapture) {
			setCapture(false);
			inplaceTooltipCapture = false;
		}
	}

	void recalculateRowHeaderHeight(GridItem item, int oldHeight, int newHeight) {
		checkWidget();

		if (newHeight > itemHeight) {
			itemHeight = newHeight;

			userModifiedItemHeight = false;
			hasDifferingHeights = false;

			itemHeight = computeItemHeight((GridItem) items.get(0), sizingGC);

			for (int cnt = 0; cnt < items.size(); cnt++)
				((GridItem) items.get(cnt)).setHeight(itemHeight);

			setScrollValuesObsolete();
			redraw();
		}

	}

	void recalculateRowHeaderWidth(GridItem item, int oldWidth, int newWidth) {
		if (!isAutoWidth()) return;

		if (newWidth > rowHeaderWidth) {
			rowHeaderWidth = newWidth;
		} else if (newWidth < rowHeaderWidth && oldWidth == rowHeaderWidth) {
			// if the changed width is smaller, and the previous width of that rows header was equal
			// to the current row header width then its possible that we may need to make the new
			// row header width smaller, but to do that we need to ask all the rows all over again
			for (Iterator iter = items.iterator(); iter.hasNext();) {
				GridItem iterItem = (GridItem) iter.next();
				newWidth = Math.max(newWidth, rowHeaderRenderer.computeSize(sizingGC, SWT.DEFAULT, SWT.DEFAULT, iterItem).x);
			}

			rowHeaderWidth = newWidth;
		}
		redraw();
	}

	/**
	 * {@inheritDoc}
	 */
	public void setFont(Font font) {
		super.setFont(font);
		sizingGC.setFont(font);
	}

	/**
	 * Returns the row header width or 0 if row headers are not visible.
	 * 
	 * @return the width of the row headers
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getItemHeaderWidth() {
		checkWidget();
		if (!rowHeaderVisible) return 0;
		return rowHeaderWidth;
	}

	/**
	 * Sets the row header width to the specified value. This automatically disables the auto width feature of the grid.
	 * 
	 * @param width the width of the row header
	 * @see #getItemHeaderWidth()
	 * @see #setAutoWidth(boolean)
	 */
	public void setItemHeaderWidth(int width) {
		checkWidget();
		rowHeaderWidth = width;
		setAutoWidth(false);
		redraw();
	}

	/**
	 * Sets the number of items contained in the receiver.
	 * 
	 * @param count the number of items
	 * 
	 * @exception org.eclipse.swt.SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 */
	public void setItemCount(int count) {
		checkWidget();
		setRedraw(false);
		if (count < 0) count = 0;

		if (count < items.size()) {
			// TODO delete and clear items if necessary
		}

		while (count > items.size()) {
			new GridItem(this, SWT.NONE);
		}
		setRedraw(true);
	}

	/**
	 * Initialize accessibility.
	 */
	private void initAccessible() {
		final Accessible accessible = getAccessible();
		accessible.addAccessibleListener(new AccessibleAdapter() {
			public void getDescription(AccessibleEvent e) {
				int childID = e.childID;
				if (childID >= 0 && childID < items.size()) {
					String descrption = "";
					for (int i = 0; i < columns.size(); i++) {
						if (i != 0) {
							descrption += ((GridColumn) columns.get(i)).getText() + " : ";
							descrption += ((GridItem) items.get(childID)).getText(i) + " ";
						}
					}
					e.result = descrption;
				}
			}

			public void getName(AccessibleEvent e) {
				int childID = e.childID;
				if (childID >= 0 && childID < items.size()) {
					// Name of the items
					e.result = ((GridItem) items.get(childID)).getText();
				} else if (childID >= items.size() && childID < items.size() + columns.size()) {
					// Name of the column headers
					e.result = ((GridColumn) columns.get(childID - items.size())).getText();
				} else if (childID >= items.size() + columns.size() && childID < items.size() + columns.size() + columnGroups.length) {
					// Name of the column group headers
					e.result = ((GridColumnGroup) columnGroups[childID - items.size() - columns.size()]).getText();
				} else if (childID >= items.size() + columns.size() + columnGroups.length && childID < items.size() + columns.size() + columnGroups.length + columnGroups.length) {
					// Name of the toggle button for column group headers
					e.result = ACC_TOGGLE_BUTTON_NAME;
				}
			}
		});

		accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
			public void getChildAtPoint(AccessibleControlEvent e) {
				Point location = toControl(e.x, e.y);
				e.childID = ACC.CHILDID_SELF;

				// Grid Items
				GridItem item = getItem(location);
				if (item != null) {
					for (int i = 0; i < getItems().length; i++) {
						if (item.equals(getItem(i))) {
							e.childID = i;
							return;
						}
					}
				} else {
					// Column Headers
					GridColumn column = overColumnHeader(location.x, location.y);
					if (column != null) {
						for (int i = 0; i < getColumns().length; i++) {
							if (column.equals(getColumn(i))) {
								e.childID = getItems().length + i;
								return;
							}
						}
					} else {
						// Column Group headers
						GridColumnGroup columnGroup = overColumnGroupHeader(location.x, location.y);
						if (columnGroup != null) {
							for (int i = 0; i < getColumnGroups().length; i++) {
								if (columnGroup.equals(getColumnGroup(i))) {
									Rectangle toggle = ((DefaultColumnGroupHeaderRenderer) columnGroup.getHeaderRenderer()).getToggleBounds();
									if (toggle.contains(location.x, location.y)) {
										// Toggle button for column group
										// header
										e.childID = getItems().length + getColumns().length + getColumnGroups().length + i;
									} else {
										// Column Group header
										e.childID = getItems().length + getColumns().length + i;
									}
									return;
								}
							}
						}
					}
				}
			}

			public void getChildCount(AccessibleControlEvent e) {
				if (e.childID == ACC.CHILDID_SELF) {
					int length = items.size();

					if (isTree) {
						// Child count for parent. Here if the item parent
						// is not an other item,
						// it is consider as children of Grid
						for (int i = 0; i < items.size(); i++) {
							if (((GridItem) items.get(i)).getParentItem() != null) {
								length--;
							}
						}
					}
					e.detail = length;
				}
			}

			public void getChildren(AccessibleControlEvent e) {
				if (e.childID == ACC.CHILDID_SELF) {
					int length = items.size();
					if (isTree) {
						for (int i = 0; i < items.size(); i++) {
							if (((GridItem) items.get(i)).getParentItem() != null) {
								length--;
							}
						}

						Object[] children = new Object[length];
						int j = 0;

						for (int i = 0; i < items.size(); i++) {
							if (((GridItem) items.get(i)).getParentItem() == null) {
								children[j] = new Integer(i);
								j++;
							}
						}
						e.children = children;
					} else {
						Object[] children = new Object[length];
						for (int i = 0; i < items.size(); i++) {
							children[i] = new Integer(i);
						}
						e.children = children;
					}
				}
			}

			public void getDefaultAction(AccessibleControlEvent e) {
				int childID = e.childID;
				if (childID >= 0 && childID < items.size()) {
					if (getItem(childID).hasChildren()) {
						// Action of tree items
						if (getItem(childID).isExpanded()) {
							e.result = ACC_ITEM_ACTION_COLLAPSE;
						} else {
							e.result = ACC_ITEM_ACTION_EXPAND;
						}
					} else {
						// action of default items
						e.result = ACC_ITEM_DEFAULT_ACTION;
					}
				} else if (childID >= items.size() && childID < items.size() + columns.size() + columnGroups.length) {
					// action of column and column group header
					e.result = ACC_COLUMN_DEFAULT_ACTION;
				} else if (childID >= items.size() + columns.size() + columnGroups.length && childID < items.size() + columns.size() + columnGroups.length + columnGroups.length) {
					// action of toggle button of column group header
					e.result = SWT.getMessage("SWT_Press");
				}
			}

			public void getLocation(AccessibleControlEvent e) {
				// location of parent
				Rectangle location = getBounds();
				location.x = 0;
				location.y = 0;
				int childID = e.childID;

				if (childID >= 0 && childID < items.size()) {
					// location of items
					GridItem item = getItem(childID);
					if (item != null) {
						Point p = getOrigin((GridColumn) columns.get(0), item);
						location.y = p.y;
						location.height = item.getHeight();
					}
				} else if (childID >= items.size() && childID < items.size() + columns.size()) {
					// location of columns headers
					GridColumn column = getColumn(childID - items.size());
					if (column != null) {
						location.x = getColumnHeaderXPosition(column);
						if (column.getColumnGroup() == null) {
							location.y = 0;
						} else {
							location.y = groupHeaderHeight;
						}
						location.height = headerHeight;
						location.width = column.getWidth();
					}
				} else if (childID >= items.size() + columns.size() && childID < items.size() + columns.size() + columnGroups.length) {
					// location of column group header
					GridColumnGroup columnGroup = getColumnGroup(childID - items.size() - columns.size());
					if (columnGroup != null) {
						location.y = 0;
						location.height = groupHeaderHeight;
						location.x = getColumnHeaderXPosition(columnGroup.getFirstVisibleColumn());
						int width = 0;
						for (int i = 0; i < columnGroup.getColumns().length; i++) {
							if (columnGroup.getColumns()[i].isVisible()) {
								width += columnGroup.getColumns()[i].getWidth();
							}
						}
						location.width = width;
					}
				} else if (childID >= items.size() + columns.size() + columnGroups.length && childID < items.size() + columns.size() + columnGroups.length + columnGroups.length) {
					// location of toggle button of column group header
					GridColumnGroup columnGroup = getColumnGroup(childID - items.size() - columns.size() - columnGroups.length);
					location = ((DefaultColumnGroupHeaderRenderer) columnGroup.getHeaderRenderer()).getToggleBounds();
				}

				if (location != null) {
					Point pt = toDisplay(location.x, location.y);
					e.x = pt.x;
					e.y = pt.y;
					e.width = location.width;
					e.height = location.height;
				}
			}

			public void getRole(AccessibleControlEvent e) {
				int childID = e.childID;
				if (childID >= 0 && childID < items.size()) {
					// role of items
					if (isTree) {
						e.detail = ACC.ROLE_TREEITEM;
					} else {
						e.detail = ACC.ROLE_LISTITEM;
					}
				} else if (childID >= items.size() && childID < items.size() + columns.size() + columnGroups.length) {
					// role of columns headers and column group headers
					e.detail = ACC.ROLE_TABLECOLUMNHEADER;
				} else if (childID >= items.size() + columns.size() + columnGroups.length && childID < items.size() + columns.size() + columnGroups.length + columnGroups.length) {
					// role of toggle button of column group headers
					e.detail = ACC.ROLE_PUSHBUTTON;
				} else if (childID == ACC.CHILDID_SELF) {
					// role of parent
					if (isTree) {
						e.detail = ACC.ROLE_TREE;
					} else {
						e.detail = ACC.ROLE_TABLE;
					}
				}
			}

			public void getSelection(AccessibleControlEvent e) {
				e.childID = ACC.CHILDID_NONE;
				if (selectedItems.size() == 1) {
					// Single selection
					e.childID = indexOf(((GridItem) selectedItems.get(0)));
				} else if (selectedItems.size() > 1) {
					// multiple selection
					e.childID = ACC.CHILDID_MULTIPLE;
					int length = selectedItems.size();
					Object[] children = new Object[length];

					for (int i = 0; i < length; i++) {
						GridItem item = (GridItem) selectedItems.get(i);
						children[i] = new Integer(indexOf(item));
					}
					e.children = children;
				}
			}

			public void getState(AccessibleControlEvent e) {
				int childID = e.childID;
				if (childID >= 0 && childID < items.size()) {
					// state of items
					e.detail = ACC.STATE_SELECTABLE;
					if (getDisplay().getActiveShell() == getParent().getShell()) {
						e.detail |= ACC.STATE_FOCUSABLE;
					}

					if (selectedItems.contains(getItem(childID))) {
						e.detail |= ACC.STATE_SELECTED;
						if (getDisplay().getActiveShell() == getParent().getShell()) {
							e.detail |= ACC.STATE_FOCUSED;
						}
					}

					if (getItem(childID).getChecked()) {
						e.detail |= ACC.STATE_CHECKED;
					}

					// only for tree type items
					if (getItem(childID).hasChildren()) {
						if (getItem(childID).isExpanded()) {
							e.detail |= ACC.STATE_EXPANDED;
						} else {
							e.detail |= ACC.STATE_COLLAPSED;
						}
					}

					if (!getItem(childID).isVisible()) {
						e.detail |= ACC.STATE_INVISIBLE;
					}
				} else if (childID >= items.size() && childID < items.size() + columns.size() + columnGroups.length) {
					// state of column headers and column group headers
					e.detail = ACC.STATE_READONLY;
				} else if (childID >= items.size() + columns.size() + columnGroups.length && childID < items.size() + columns.size() + columnGroups.length + columnGroups.length) {
					// state of toggle button of column group headers
					if (getColumnGroup(childID - items.size() - columns.size() - columnGroups.length).getExpanded()) {
						e.detail = ACC.STATE_EXPANDED;
					} else {
						e.detail = ACC.STATE_COLLAPSED;
					}
				}
			}

			public void getValue(AccessibleControlEvent e) {
				int childID = e.childID;
				if (childID >= 0 && childID < items.size()) {
					// value for tree items
					if (isTree) {
						e.result = "" + getItem(childID).getLevel();
					}
				}
			}
		});

		addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event event) {
				if (selectedItems.size() > 0) {
					accessible.setFocus(items.indexOf(selectedItems.get(selectedItems.size() - 1)));
				}
			}
		});

		addTreeListener(new TreeListener() {
			public void treeCollapsed(TreeEvent e) {
				if (getFocusItem() != null) {
					accessible.setFocus(items.indexOf(getFocusItem()));
				}
			}

			public void treeExpanded(TreeEvent e) {
				if (getFocusItem() != null) {
					accessible.setFocus(items.indexOf(getFocusItem()));
				}
			}
		});
	}

	/**
	 * @return the disposing
	 */
	boolean isDisposing() {
		return disposing;
	}

	/**
	 * @param hasSpanning the hasSpanning to set
	 */
	void setHasSpanning(boolean hasSpanning) {
		this.hasSpanning = hasSpanning;
	}

	/**
	 * Returns the receiver's tool tip text, or null if it has not been set.
	 * 
	 * @return the receiver's tool tip text
	 * 
	 * @exception SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 */
	public String getToolTipText() {
		checkWidget();
		return toolTipText;
	}

	/**
	 * Sets the receiver's tool tip text to the argument, which may be null indicating that no tool tip text should be shown.
	 * 
	 * @param string the new tool tip text (or null)
	 * 
	 * @exception SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 */
	public void setToolTipText(String string) {
		checkWidget();
		toolTipText = string;
	}

	/**
	 * Updates the row height when the first image is set on an item.
	 * 
	 * @param column the column the image is change
	 * @param item item which images has just been set on.
	 */
	void imageSetOnItem(int column, GridItem item) {
		if (sizeOnEveryItemImageChange) {
			if (item == null || item.getImage(column) == null) return;

			int height = item.getImage(column).getBounds().height;
			// FIXME Needs better algorithm
			if (height + 20 > getItemHeight()) {
				height = computeItemHeight(item, sizingGC);
				setItemHeight(height);
			}
		} else {
			if (firstImageSet || userModifiedItemHeight) return;

			int height = computeItemHeight(item, sizingGC);
			setItemHeight(height);

			firstImageSet = true;
		}
	}

	/**
	 * Determines if the mouse is hovering on the selection drag area and changes the pointer and sets field appropriately.
	 * <p>
	 * Note: The 'selection drag area' is that part of the selection, on which a drag event can be initiated. This is either the border of the selection (i.e. a cell border between a slected and a
	 * non-selected cell) or the complete selection (i.e. anywhere on a selected cell). What area serves as drag area is determined by {@link #setDragOnFullSelection(boolean)}.
	 * 
	 * @param x
	 * @param y
	 * @return
	 * @see #setDragOnFullSelection(boolean)
	 */
	private boolean handleHoverOnSelectionDragArea(int x, int y) {
		boolean over = false;
		// Point inSelection = null;

		if ((!rowHeaderVisible || x > rowHeaderWidth - SELECTION_DRAG_BORDER_THRESHOLD) && (!columnHeadersVisible || y > headerHeight - SELECTION_DRAG_BORDER_THRESHOLD)) {
			// not on a header

			// if(!dragOnFullSelection)
			// {
			// // drag area is the border of the selection
			//
			// if(cellSelectionEnabled)
			// {
			// Point neP = new Point( x-SELECTION_DRAG_BORDER_THRESHOLD, y-SELECTION_DRAG_BORDER_THRESHOLD );
			// Point ne = getCell(neP);
			// Point nwP = new Point( x+SELECTION_DRAG_BORDER_THRESHOLD, y-SELECTION_DRAG_BORDER_THRESHOLD );
			// Point nw = getCell(nwP);
			// Point swP = new Point( x+SELECTION_DRAG_BORDER_THRESHOLD, y+SELECTION_DRAG_BORDER_THRESHOLD );
			// Point sw = getCell(swP);
			// Point seP = new Point( x-SELECTION_DRAG_BORDER_THRESHOLD, y+SELECTION_DRAG_BORDER_THRESHOLD );
			// Point se = getCell(seP);
			//
			// boolean neSel = ne != null && isCellSelected(ne);
			// boolean nwSel = nw != null && isCellSelected(nw);
			// boolean swSel = sw != null && isCellSelected(sw);
			// boolean seSel = se != null && isCellSelected(se);
			//
			// over = (neSel || nwSel || swSel || seSel) && (!neSel || !nwSel || !swSel || !seSel);
			// // inSelection = neSel ? neP : nwSel ? nwP : swSel ? swP : seSel ? seP : null;
			// }
			// else
			// {
			// Point nP = new Point( x, y-SELECTION_DRAG_BORDER_THRESHOLD );
			// GridItem n = getItem(nP);
			// Point sP = new Point( x, y+SELECTION_DRAG_BORDER_THRESHOLD );
			// GridItem s = getItem(sP);
			//
			// boolean nSel = n != null && isSelected(n);
			// boolean sSel = s != null && isSelected(s);
			//
			// over = nSel != sSel;
			// // inSelection = nSel ? nP : sSel ? sP : null;
			// }
			// }
			// else
			// {
			// drag area is the entire selection

			if (cellSelectionEnabled) {
				Point p = new Point(x, y);
				Point cell = getCell(p);
				over = cell != null && isCellSelected(cell);
				// inSelection = over ? p : null;
			} else {
				Point p = new Point(x, y);
				GridItem item = getItem(p);
				over = item != null && isSelected(item);
				// inSelection = over ? p : null;
			}
		}
		// }

		if (over != hoveringOnSelectionDragArea) {
			// if (over)
			// {
			// // use drag cursor only in border mode
			// if (!dragOnFullSelection)
			// setCursor(getDisplay().getSystemCursor(SWT.CURSOR_SIZEALL));
			// // potentialDragStart = inSelection;
			// }
			// else
			// {
			// setCursor(null);
			// // potentialDragStart = null;
			// }
			hoveringOnSelectionDragArea = over;
		}
		return over;
	}

	/**
	 * Display a mark indicating the point at which an item will be inserted. This is used as a visual hint to show where a dragged item will be inserted when dropped on the grid. This method should
	 * not be called directly, instead {@link DND#FEEDBACK_INSERT_BEFORE} or {@link DND#FEEDBACK_INSERT_AFTER} should be set in {@link DropTargetEvent#feedback} from within a
	 * {@link DropTargetListener}.
	 * 
	 * @param item the insert item. Null will clear the insertion mark.
	 * @param column the column of the cell. Null will make the insertion mark span all columns.
	 * @param before true places the insert mark above 'item'. false places the insert mark below 'item'.
	 * 
	 * @exception IllegalArgumentException <ul>
	 *                <li>ERROR_INVALID_ARGUMENT - if the item or column has been disposed</li>
	 *                </ul>
	 * @exception SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 */
	public void setInsertMark(GridItem item, GridColumn column, boolean before) {
		checkWidget();
		if (item != null) {
			if (item.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}
		if (column != null) {
			if (column.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}
		insertMarkItem = item;
		insertMarkColumn = column;
		insertMarkBefore = before;
		redraw();
	}

	/**
	 * A helper method for {@link GridDropTargetEffect#dragOver(DropTargetEvent)}.
	 * 
	 * @param point
	 * @return true if point is near the top or bottom border of the visible grid area
	 */
	public boolean isInDragScrollArea(Point point) {
		int rhw = rowHeaderVisible ? rowHeaderWidth : 0;
		int chh = columnHeadersVisible ? headerHeight : 0;
		Rectangle top = new Rectangle(rhw, chh, getClientArea().width - rhw, DRAG_SCROLL_AREA_HEIGHT);
		Rectangle bottom = new Rectangle(rhw, getClientArea().height - DRAG_SCROLL_AREA_HEIGHT, getClientArea().width - rhw, DRAG_SCROLL_AREA_HEIGHT);
		return top.contains(point) || bottom.contains(point);
	}

	/**
	 * Clears the item at the given zero-relative index in the receiver. The text, icon and other attributes of the item are set to the default value. If the table was created with the
	 * <code>SWT.VIRTUAL</code> style, these attributes are requested again as needed.
	 * 
	 * @param index the index of the item to clear
	 * @param allChildren <code>true</code> if all child items of the indexed item should be cleared recursively, and <code>false</code> otherwise
	 * 
	 * @exception IllegalArgumentException <ul>
	 *                <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *                </ul>
	 * @exception SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 * 
	 * @see SWT#VIRTUAL
	 * @see SWT#SetData
	 */
	public void clear(int index, boolean allChildren) {
		checkWidget();
		if (index < 0 || index >= items.size()) {
			SWT.error(SWT.ERROR_INVALID_RANGE);
		}

		GridItem item = getItem(index);
		item.clear(allChildren);
		redraw();
	}

	/**
	 * Clears the items in the receiver which are between the given zero-relative start and end indices (inclusive). The text, icon and other attributes of the items are set to their default values.
	 * If the table was created with the <code>SWT.VIRTUAL</code> style, these attributes are requested again as needed.
	 * 
	 * @param start the start index of the item to clear
	 * @param end the end index of the item to clear
	 * @param allChildren <code>true</code> if all child items of the range of items should be cleared recursively, and <code>false</code> otherwise
	 * 
	 * @exception IllegalArgumentException <ul>
	 *                <li>ERROR_INVALID_RANGE - if either the start or end are not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *                </ul>
	 * @exception SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 * 
	 * @see SWT#VIRTUAL
	 * @see SWT#SetData
	 */
	public void clear(int start, int end, boolean allChildren) {
		checkWidget();
		if (start > end) return;

		int count = items.size();
		if (!(0 <= start && start <= end && end < count)) {
			SWT.error(SWT.ERROR_INVALID_RANGE);
		}
		for (int i = start; i <= end; i++) {
			GridItem item = (GridItem) items.get(i);
			item.clear(allChildren);
		}
		redraw();
	}

	/**
	 * Clears the items at the given zero-relative indices in the receiver. The text, icon and other attributes of the items are set to their default values. If the table was created with the
	 * <code>SWT.VIRTUAL</code> style, these attributes are requested again as needed.
	 * 
	 * @param indices the array of indices of the items
	 * @param allChildren <code>true</code> if all child items of the indexed items should be cleared recursively, and <code>false</code> otherwise
	 * 
	 * @exception IllegalArgumentException <ul>
	 *                <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
	 *                <li>ERROR_NULL_ARGUMENT - if the indices array is null</li>
	 *                </ul>
	 * @exception SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 * 
	 * @see SWT#VIRTUAL
	 * @see SWT#SetData
	 */
	public void clear(int[] indices, boolean allChildren) {
		checkWidget();
		if (indices == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		if (indices.length == 0) return;

		int count = items.size();
		for (int i = 0; i < indices.length; i++) {
			if (!(0 <= indices[i] && indices[i] < count)) {
				SWT.error(SWT.ERROR_INVALID_RANGE);
			}
		}
		for (int i = 0; i < indices.length; i++) {
			GridItem item = (GridItem) items.get(indices[i]);
			item.clear(allChildren);
		}
		redraw();
	}

	/**
	 * Clears all the items in the receiver. The text, icon and other attributes of the items are set to their default values. If the table was created with the <code>SWT.VIRTUAL</code> style, these
	 * attributes are requested again as needed.
	 * 
	 * @param allChildren <code>true</code> if all child items of each item should be cleared recursively, and <code>false</code> otherwise
	 * 
	 * @exception SWTException <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *                </ul>
	 * 
	 * @see SWT#VIRTUAL
	 * @see SWT#SetData
	 */
	public void clearAll(boolean allChildren) {
		checkWidget();
		if (items.size() > 0) clear(0, items.size() - 1, allChildren);
	}

	/**
	 * Recalculate the height of the header
	 */
	public void recalculateHeader() {
		int previous = getHeaderHeight();
		computeHeaderHeight(sizingGC);

		if (previous != getHeaderHeight()) {
			scrollValuesObsolete = true;
			redraw();
		}
	}

	/**
	 * Query the grid for all currently visible rows and columns
	 * <p>
	 * <b>This support is provisional and may change</b>
	 * </p>
	 * 
	 * @return all currently visible rows and columns
	 */
	public GridVisibleRange getVisibleRange() {
		// FIXME I think we should remember the topIndex in the onPaint-method
		int topIndex = getTopIndex();
		int bottomIndex = getBottomIndex();
		int startColumnIndex = getStartColumnIndex();
		int endColumnIndex = getEndColumnIndex();

		GridVisibleRange range = new GridVisibleRange();
		range.items = new GridItem[0];
		range.columns = new GridColumn[0];

		if (topIndex <= bottomIndex) {
			if (items.size() > 0) {
				range.items = new GridItem[bottomIndex - topIndex + 1];
				for (int i = topIndex; i <= bottomIndex; i++) {
					range.items[i - topIndex] = (GridItem) items.get(i);
				}
			}
		}

		if (startColumnIndex <= endColumnIndex) {
			if (displayOrderedColumns.size() > 0) {
				ArrayList cols = new ArrayList();
				for (int i = startColumnIndex; i <= endColumnIndex; i++) {
					GridColumn col = (GridColumn) displayOrderedColumns.get(i);
					if (col.isVisible()) {
						cols.add(col);
					}
				}

				range.columns = new GridColumn[cols.size()];
				cols.toArray(range.columns);
			}
		}

		return range;
	}

	int getStartColumnIndex() {
		checkWidget();

		if (startColumnIndex != -1) {
			return startColumnIndex;
		}

		if (!hScroll.getVisible()) {
			startColumnIndex = 0;
		}

		startColumnIndex = hScroll.getSelection();

		return startColumnIndex;
	}

	int getEndColumnIndex() {
		checkWidget();

		if (endColumnIndex != -1) {
			return endColumnIndex;
		}

		if (displayOrderedColumns.size() == 0) {
			endColumnIndex = 0;
		} else if (getVisibleGridWidth() < 1) {
			endColumnIndex = getStartColumnIndex();
		} else {
			int x = 0;
			x -= getHScrollSelectionInPixels();

			if (rowHeaderVisible) {
				// row header is actually painted later
				x += rowHeaderWidth;
			}

			int startIndex = getStartColumnIndex();
			GridColumn[] columns = new GridColumn[displayOrderedColumns.size()];
			displayOrderedColumns.toArray(columns);

			for (int i = startIndex; i < columns.length; i++) {
				endColumnIndex = i;
				GridColumn column = columns[i];

				if (column.isVisible()) {
					x += column.getWidth();
				}

				if (x > getClientArea().width) {

					break;
				}
			}

		}

		endColumnIndex = Math.max(0, endColumnIndex);

		return endColumnIndex;
	}

	public void setSizeOnEveryItemImageChange(boolean sizeOnEveryItemImageChange) {
		this.sizeOnEveryItemImageChange = sizeOnEveryItemImageChange;
	}

	/**
	 * Returns the width of the row headers.
	 * 
	 * @return width of the column header row
	 * @throws org.eclipse.swt.SWTException <ul>
	 *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
	 *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
	 *             </ul>
	 */
	public int getRowHeaderWidth() {
		checkWidget();
		return rowHeaderWidth;
	}

	/**
	 * Sets the value of the auto-height feature. When enabled, this feature resizes the height of rows to reflect the content of cells with word-wrapping enabled. Cell word-wrapping is enabled via
	 * the GridColumn.setWordWrap(boolean) method. If column headers have word-wrapping enabled, this feature will also resize the height of the column headers as necessary.
	 * 
	 * @param enabled Set to true to enable this feature, false (default) otherwise.
	 */
	public void setAutoHeight(boolean enabled) {
		if (autoHeight == enabled) return;

		checkWidget();
		autoHeight = enabled;
		setRowsResizeable(false); // turn of resizing of row height since it conflicts with this property
		redraw();
	}

	/**
	 * Returns the value of the auto-height feature, which resizes row heights and column header heights based on word-wrapped content.
	 * 
	 * @return Returns whether or not the auto-height feature is enabled.
	 * @see #setAutoHeight(boolean)
	 */
	public boolean isAutoHeight() {
		return autoHeight;
	}

	/**
	 * Sets the value of the auto-width feature. When enabled, this feature resizes the width of the row headers to reflect the content of row headers.
	 * 
	 * @param enabled Set to true to enable this feature, false (default) otherwise.
	 * @see #isAutoWidth()
	 */
	public void setAutoWidth(boolean enabled) {
		if (autoWidth == enabled) return;

		checkWidget();
		autoWidth = enabled;
		redraw();
	}

	/**
	 * Returns the value of the auto-height feature, which resizes row header width based on content.
	 * 
	 * @return Returns whether or not the auto-width feature is enabled.
	 * @see #setAutoWidth(boolean)
	 */
	public boolean isAutoWidth() {
		return autoWidth;
	}

	/**
	 * Sets the value of the word-wrap feature for row headers. When enabled, this feature will word-wrap the contents of row headers.
	 * 
	 * @param enabled Set to true to enable this feature, false (default) otherwise.
	 * @see #isWordWrapHeader()
	 */
	public void setWordWrapHeader(boolean enabled) {
		if (wordWrapRowHeader == enabled) return;

		checkWidget();
		wordWrapRowHeader = enabled;
		redraw();
	}

	/**
	 * Returns the value of the row header word-wrap feature, which word-wraps the content of row headers.
	 * 
	 * @return Returns whether or not the row header word-wrap feature is enabled.
	 * @see #setWordWrapHeader(boolean)
	 */
	public boolean isWordWrapHeader() {
		return wordWrapRowHeader;
	}

	public boolean isHoveringOnColumnResizer() {
		return hoveringOnColumnResizer;
	}

	public void setHoveringOnColumnResizer(boolean hoveringOnColumnResizer) {
		this.hoveringOnColumnResizer = hoveringOnColumnResizer;
	}

	public boolean isHoveringOnRowResizer() {
		return hoveringOnRowResizer;
	}

	public void setHoveringOnRowResizer(boolean hoveringOnRowResizer) {
		this.hoveringOnRowResizer = hoveringOnRowResizer;
	}
	
	
}
